init 方法 — patch方法分析(二)
最后更新于:2022-04-02 08:12:21
>[success] # patch 方法分析(二)
~~~
1.上一个章节分析后,patch更新主要分为两块,一块是新老虚拟dom相同,一块是不同,这里是对
不同位置的分析
~~~
>[info] ## 分析patch 里面createElm
~~~
注意:这一步只是将虚拟dom 的结构创建为真实dom保存在虚拟dom属性的'elm'中,此时并没有
将虚拟dom构建的dom插入整个页面中
1.这部分做了:
1.1.返回创建的 DOM 元素这也就是代码中的'vnode.elm'
1.2.创建 vnode 对应的 DOM 元素
2.执行顺序:
2.1.首先触发用户设置的 init 钩子函数
2.2.如果选择器是!,创建评论节点
2.3.如果选择器为空,创建文本节点
2.4.如果选择器不为空
2.4.1.解析选择器,设置标签的 id 和 class 属性
2.4.2.执行模块的 create 钩子函数
2.4.3.如果 vnode 有 children,创建子 vnode 对应的 DOM,追加到 DOM 树
2.4.4.如果 vnode 的 text 值是 string/number,创建文本节点并追击到 DOM 树
2.4.5.执行用户设置的 create 钩子函数
2.4.6.如果有用户设置的 insert 钩子函数,把 vnode 添加到队列中
3.源码位置:src/snabbdom.ts
~~~
>[danger] ##### 代码分析
~~~ts
function createElm(vnode: VNode, insertedVnodeQueue: VNodeQueue): Node {
let i: any, data = vnode.data;
if (data !== undefined) {
// 执行用户设置的 init 钩子函数
const init = data.hook ? .init;
if (isDef(init)) {
init(vnode);
data = vnode.data;
}
}
let children = vnode.children,
sel = vnode.sel;
if (sel === '!') {
// 如果选择器是!,创建评论节点
if (isUndef(vnode.text)) {
vnode.text = '';
}
vnode.elm = api.createComment(vnode.text!);
} else if (sel !== undefined) {
// 如果选择器不为空
// 解析选择器
// Parse selector
const hashIdx = sel.indexOf('#');
const dotIdx = sel.indexOf('.', hashIdx);
const hash = hashIdx > 0 ? hashIdx : sel.length;
const dot = dotIdx > 0 ? dotIdx : sel.length;
const tag = hashIdx !== -1 || dotIdx !== -1 ? sel.slice(0,
Math.min(hash, dot)) : sel;
const elm = vnode.elm = isDef(data) && isDef(i = data.ns) ?
api.createElementNS(i, tag) :
api.createElement(tag);
// 在这之前都是对虚拟dom 的sel属性进行解析,为了对提供的sel 选择器而创建dom准备的
// 注意创建真实dom 有两种一种是createElementNS这是创建svg,一种是createElement 创建普通dom
// ---------------------------------------------
if (hash < dot) elm.setAttribute('id', sel.slice(hash + 1, dot));
if (dotIdx > 0) elm.setAttribute('class', sel.slice(dot +
1).replace(/\./g, ' '));
// 执行模块的 create 钩子函数
for (i = 0; i < cbs.create.length; ++i) cbs.create[i](emptyNode,
vnode);
// 如果 vnode 中有子节点,创建子 vnode 对应的 DOM 元素并追加到 DOM 树上
if (is.array(children)) {
for (i = 0; i < children.length; ++i) {
const ch = children[i];
if (ch != null) {
api.appendChild(elm, createElm(ch as VNode,
insertedVnodeQueue));
}
}
} else if (is.primitive(vnode.text)) {
// 如果 vnode 的 text 值是 string/number,创建文本节点并追加到 DOM 树
api.appendChild(elm, api.createTextNode(vnode.text));
}
const hook = vnode.data!.hook;
if (isDef(hook)) {
// 执行用户传入的钩子 create
hook.create ? .(emptyNode, vnode);
if (hook.insert) {
// 把 vnode 添加到队列中,为后续执行 insert 钩子做准备
insertedVnodeQueue.push(vnode);
}
}
} else {
// 如果选择器为空,创建文本节点
vnode.elm = api.createTextNode(vnode.text!);
}
// 返回新创建的 DOM
return vnode.elm;
}
~~~
>[info] ## addVnodes 和 removeVnodes 函数
~~~
function addVnodes(parentElm: Node,
before: Node | null,
vnodes: Array,
startIdx: number,
endIdx: number,
insertedVnodeQueue: VNodeQueue) {
for (; startIdx <= endIdx; ++startIdx) {
const ch = vnodes[startIdx];
if (ch != null) {
api.insertBefore(parentElm, createElm(ch, insertedVnodeQueue), before);
}
}
}
~~~
~~~
function removeVnodes(parentElm: Node,
vnodes: Array,
startIdx: number,
endIdx: number): void {
for (; startIdx <= endIdx; ++startIdx) {
let i: any, listeners: number, rm: () => void, ch = vnodes[startIdx];
if (ch != null) {
// 如果 sel 有值
if (isDef(ch.sel)) {
// 执行 destroy 钩子函数(会执行所有子节点的 destroy 钩子函数)
invokeDestroyHook(ch);
listeners = cbs.remove.length + 1;
// 创建删除的回调函数
rm = createRmCb(ch.elm as Node, listeners);
for (i = 0; i < cbs.remove.length; ++i) cbs.remove[i](ch, rm);
// 执行用户设置的 remove 钩子函数
if (isDef(i = ch.data) && isDef(i = i.hook) && isDef(i = i.remove)) {
i(ch, rm);
} else {
// 如果没有用户钩子函数,直接调用删除元素的方法
rm();
}
} else { // Text node
// 如果是文本节点,直接调用删除元素的方法
api.removeChild(parentElm, ch.elm as Node);
}
}
}
}
function invokeDestroyHook(vnode: VNode) {
let i: any, j: number, data = vnode.data;
if (data !== undefined) {
// 执行用户设置的 destroy 钩子函数
if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode);
// 调用模块的 distroy 钩子函数
for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode);
// 执行子节点的 distroy 钩子函数
if (vnode.children !== undefined) {
for (j = 0; j < vnode.children.length; ++j) {
i = vnode.children[j];
if (i != null && typeof i !== "string") {
invokeDestroyHook(i);
}
}
}
}
}
function createRmCb(childElm: Node, listeners: number) {
// 返回删除元素的回调函数
return function rmCb() {
if (--listeners === 0) {
const parent = api.parentNode(childElm);
api.removeChild(parent, childElm);
}
};
}
~~~
';