五、挂载与更新
挂载子元素
DOM Properties != HTML Attributes
但是二者往往(并非所有!)一一对应,如 id="123"
对应 el.id
把 HTML Attributes 与 DOM Properties 具有相同名称(即 id)的属性看作直接映射
核心理念:
- HTML Attributes 的作用是设置与之对应的 DOM Properties 的初始值
- 一个 HTML Attribute 可以对应多个 DOM Properties
- 浏览器有矫正作用,即把不正确的属性值调整为默认值
元素属性
对于布尔类型,空字符串相当于 false
针对属性 disable,我们希望不传入任何参数(即空字符串)时,隐藏元素
这个时候就要加一个判断,当属性为布尔类型且值为空字符串时,手动设置为 true,而不是让浏览器矫正为 false!!!
class 处理
请看下方一段 DOM 以及其对应的 vnode
// 渲染结果
<p class="pig cat"></p>;
// vnode
const vnode = {
type: "p",
props: {
class: "pig cat",
},
};
设置 class 速度优劣
目前有三种设置 class 的方式,其中速度最快的是使用 el.className
- setAttribute
- el.className
- el.classList
卸载
若要结束渲染,单纯设置 innerHTML 为空是不严谨的;
应当通过 vnode 获取真实 DOM,并使用原生 DOM 操作进行卸载;
下面代码指在挂载 vnode 时自动关联真实 DOM
function mountElement(vnode, container) {
const el = (vnode.el = createElement(vnode.type));
...
}
不同类型 vnode 打补丁流程
- 如果旧节点存在且类型不等于新节点,则卸载旧节点并置 null
- vnode 可以是普通标签或者组件或者 fragment,要做出判断
function patch(n1, n2, container) {
if (n1 && n1.type !== n2.type) {
unmount(n1);
n1 = null;
}
// 代码运行到这里,证明 n1 和 n2 所描述的内容相同
const { type } = n2;
// 如果 n2.type 的值是字符串类型,则它描述的是普通标签元素
if (typeof type === "string") {
if (!n1) {
mountElement(n2, container);
} else {
patchElement(n1, n2);
}
} else if (typeof type === "object") {
// 如果 n2.type 的值的类型是对象,则它描述的是组件
} else if (type === "xxx") {
// 处理其他类型的 vnode
}
}
事件处理
普通版处理流程:
- 添加事件使用 addEventListener
- 更新事件,先 removeEventListener 移除事件,再添加新事件
进阶版处理流程:
- 绑定一个伪事件处理函数 invoker,设置真正事件处理函数为 invoker.value 的值
- 每次更新事件仅需修改 invoker.value 即可
- 使用一个对象管理所有的注册事件,避免事件之间的覆盖(原始情况下,后注册的事件会直接覆盖掉前面的)
事件冒泡处理
屏蔽所有绑定时间晚于事件触发时间的事件处理函数的执行
fragment
使用 fragment 片段来描述多个根节点
fragment 实现多根节点实际上就是把根节点全部存在 children 里面了!
描述一个模板标签 template 下存在四个 li 标签,可以用 vnode 这么表示
const vnode = {
type: Fragment,
children: [
{ type: "li", children: "1" },
{ type: "li", children: "2" },
{ type: "li", children: "3" },
],
};
fragment 本身不会被渲染,故仅渲染其子节点
小结哦!
HTML Attributes 和 DOM Properties
class 属性增强以及设置 class 的三种方式性能优劣分析
卸载操作需要注意的挂载问题
vnode 更新时判据,新旧 vnode 判断是否需要打补丁 patch
props 事件处理与伪事件 invoker 使用
绑定事件的时间与触发事件的时间