阅读须知
- 文章参考的preact版本是10.5.13
- 文章会省略大部分逻辑,比如hydrating,context,isSvg等。所以如果有大佬点进来需谨慎。
- 简略版本代码
diffChildren概览
- diffChildren循环处理children节点
- 依据不同的类型,创建生成不同的childVNode
- 对key节点处理,判断复用或者重置oldVNode
- 调用diff对比子节点
- newDom插入到parentDom
- 卸载旧节点
export function diffChildren(
parentDom, // 当前children的父节点,如果是组件那么父节点就是往上找到第一个真实dom
renderResult,
newParentVNode,
oldParentVNode,
commitQueue,
oldDom,
) {
let i, j, oldVNode, childVNode, newDom, firstChildDom;
let oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR;
let oldChildrenLength = oldChildren.length;
newParentVNode._children = [];
// 处理子节点
// 1. 创建childVNode
// 2. key对比进行节点复用
// 3. 继续diff子节点
// 4. 替换节点到dom上面
// 对不需要的节点/组件 进行卸载处理
// 执行oldChildren的vNode的卸载生命周期
}
处理子节点
for (i = 0; i < renderResult.length; i++) {
childVNode = renderResult[i];
// 依据不同的类型,创建生成不同的childVNode
// 对key这种情况节点进行对比 复用或者重置oldVNode
// diff 递归对比子节点
// 进行dom的替换
}
创建childVNode
- childNode为null、typeof childVNode == 'boolean'。该类型的节点,设置为null,不渲染。
- typeof childVNode 为string/number的节点,将创建为文本节点(vnode.type = null)。
- childVNode为数组时,将创建为Fragment节点(vnode.type = Fragment)。
- childVNode已创建时,直接克隆新的vNode。
//childVNode设置为null,不渲染
if (childVNode == null || typeof childVNode == 'boolean') {
childVNode = newParentVNode._children[i] = null;
}
// 文本节点会创建一个type==null的vNode
else if (
typeof childVNode == 'string' ||
typeof childVNode == 'number' ||
typeof childVNode == 'bigint'
) {
childVNode = newParentVNode._children[i] = createVNode(
null,
childVNode,
null,
null,
childVNode
);
}
// 子节点是数组创建一个type==Fragment的vNode
else if (Array.isArray(childVNode)) {
childVNode = newParentVNode._children[i] = createVNode(
Fragment,
{ children: childVNode },
null,
null,
null
);
}
// VNode is already in use, clone it。eg:
// const reuse = <div />
// <div>{reuse}<span />{reuse}</div>
else if (childVNode._depth > 0) {
childVNode = newParentVNode._children[i] = createVNode(
childVNode.type,
childVNode.props,
childVNode.key,
null,
childVNode._original
);
}
else {
childVNode = newParentVNode._children[i] = childVNode;
}
if (childVNode == null) {
continue;
}
// 绑定父VNode,深度比父节点+1
childVNode._parent = newParentVNode;
childVNode._depth = newParentVNode._depth + 1;
key节点对比
- 新旧节点key和type相同,oldChildren[i] = undefined 表示复用不需要卸载(后续卸载逻辑体现)
- 不同就遍历oldChildren,寻找key/value适配的节点,
- 若找到,oldChildren[i] = undefined 表示复用不需要卸载
- 找不到证明旧节点没有可复用,需要创建。设置oldVNode = null,后续diffElementNodes根据这个条件会创建真实dom。
oldVNode = oldChildren[i];
// 如果新节点和旧节点相同,oldChildren[i] = undefined
if (
oldVNode === null ||(oldVNode &&
childVNode.key == oldVNode.key &&
childVNode.type === oldVNode.type)
) {
oldChildren[i] = undefined;
} else {
// 新旧节点不同:
// 1. 如果在oldChildren中找到 oldVnode赋值为找到的某一个 从而复用oldVnode的dom
// 2. 如果在oldChildren找不到 直接oldVnode=null后续赋值{} 会导致后面diffElementNodes从oldVnode拿不出dom所以会重新创建
for (j = 0; j < oldChildrenLength; j++) {
oldVNode = oldChildren[j];
if (
oldVNode &&
childVNode.key == oldVNode.key &&
childVNode.type === oldVNode.type
) {
oldChildren[j] = undefined;
break;
}
oldVNode = null;
}
}
oldVNode = oldVNode || EMPTY_OBJ;
diff 对比子节点
diff(
parentDom,
childVNode,
oldVNode,
commitQueue,
oldDom,
);
dom的替换
newDom = childVNode._dom;
if (!newDom) {
firstChildDom = firstChildDom || newDom;
// 组件只有在孩子节点相同才需要递归比较子节点
if (
typeof childVNode.type === 'function' &&
childVNode._children === oldVNode._children
) {
childVNode._nextDom = oldDom = reorderChildren(
childVNode,
oldDom,
parentDom,
);
}
// 其他都直接替换
else {
oldDom = placeChild(
parentDom,
childVNode,
oldVNode,
oldChildren,
newDom,
oldDom,
);
}
}
reorderChildren(用于组件)
- 组件会去递归调用,dom节点会调用placeChild
function reorderChildren(childVNode, oldDom, parentDom) {
for (let tmp = 0; tmp < childVNode._children.length; tmp++) {
let vnode = childVNode._children[tmp];
if (vnode) {
vnode._parent = childVNode;
if (typeof vnode.type == 'function') {
oldDom = reorderChildren(vnode, oldDom, parentDom);
} else {
oldDom = placeChild(
parentDom,
vnode,
vnode,
childVNode._children,
vnode._dom,
oldDom
);
}
}
}
return oldDom;
}
placeChild方法 (用于dom节点)
- appendChild情况:
- 首次渲染的时候没有oldVNode,所以也就没有oldDom。
- 新建一个节点,这个节点不是复用旧节点(key旧节点复用),也没有oldDom。
- insertBefore的情况
- 节点被复用了,key相同导致节点被复用,此时不需要删除节点,插入旧节点前面。
- 返回oldDom的下一个节点,作为下一个节点的oldDom
function placeChild(
parentDom,
childVNode,
oldVNode,
oldChildren,
newDom,
oldDom
) {
let nextDom;
// todo:不知道为什么只对Fragments起作用
if (childVNode._nextDom !== undefined) {
nextDom = childVNode._nextDom;
childVNode._nextDom = undefined;
} else if (
oldVNode == null ||
newDom != oldDom ||
newDom.parentNode == null
) {
// 首次渲染或者新增节点oldDom是不存在 但是可能是交换节点所以要保证父节点不一样
outer: if (oldDom == null || oldDom.parentNode !== parentDom) {
parentDom.appendChild(newDom);
nextDom = null;
}
// 主要对应上文key复用旧节点,主要是交换节点的逻辑
else {
// 当前兄弟节点是否找到newDom节点,若找到,中断执行。
// todo:不知道为什么是j+=2
for (
let sibDom = oldDom, j = 0;
(sibDom = sibDom.nextSibling) && j < oldChildren.length;
j += 2
) {
if (sibDom == newDom) {
break outer;
}
}
parentDom.insertBefore(newDom, oldDom);
nextDom = oldDom;
}
}
// 返回dom的下一个节点
if (nextDom !== undefined) {
oldDom = nextDom;
} else {
oldDom = newDom.nextSibling;
}
return oldDom;
}
节点appendChild逻辑(图一)和insertBefore逻辑(图2)
替换逻辑
- 赋值firstChildDom,进行dom的替换。
if (newDom != null) {
if (firstChildDom == null) {
firstChildDom = newDom;
}
// 组件的替换
if (
typeof childVNode.type == 'function' &&
childVNode._children != null && // Can be null if childVNode suspended
childVNode._children === oldVNode._children
) {
childVNode._nextDom = oldDom = reorderChildren(
childVNode,
oldDom,
parentDom
);
}
// 普通节点的替换
else {
oldDom = placeChild(
parentDom,
childVNode,
oldVNode,
oldChildren,
newDom,
oldDom
);
}
}
// _dom赋值第一个子节点
newParentVNode._dom = firstChildDom;
卸载处理
- 对vNode节点以及其子节点递归执行componentWillUnMount,
- 在dom树上移除oldChildren
for (i = oldChildrenLength; i--; ) {
if (oldChildren[i] != null) {
unmount(oldChildren[i], oldChildren[i]);
}
}
export function unmount(vnode, parentVNode, skipRemove) {
let r;
let dom;
if (!skipRemove && typeof vnode.type != 'function') {
skipRemove = (dom = vnode._dom) != null;
}
// 执行vNode的componentWillUnmount
if ((r = vnode._component) != null) {
if (r.componentWillUnmount) {
try {
r.componentWillUnmount();
} catch (e) {
options._catchError(e, parentVNode);
}
}
r.base = r._parentDom = null;
}
// 递归执行子组件的componentWillUnmount
if ((r = vnode._children)) {
for (let i = 0; i < r.length; i++) {
if (r[i]) unmount(r[i], parentVNode, skipRemove);
}
}
// 在dom树上移除oldChildren
if (dom != null) removeNode(dom);
}