跳到主要内容

认识

2023年06月10日
柏拉文
越努力,越幸运

一、认识


patchElement 主要用于更新子节点, 主要实现如下所示:

const processElement = (n1, n2, container,anchor) => {
if(n1 == null){
mountElement(n2,container,anchor);
}else{
patchElement(n1,n2,parentComponent);
}
};

const patchElement = (n1,n2,patchComponent)=>{
const el = n2.el = n1.el;
const oldProps = n1 && n1.props;
const newProps = n2.props;

/**
* @description: 更新 children
*/
patchChildren(n1,n2,el,null);
/**
* @description: 更新 props
*/
patchProps(el,n2,oldProps,newProps);
}

二、patchChildren 细节


patchElement 中最核心的部分是对元素进行打补丁。基本实现如下所示:

const patchChildren = (n1, n2, container, anchor) => {
const c1 = n1 && n1.children;
const c2 = n2.children;

if (typeof c2 === 'string') {
/**
* @description: 分支一: 如果新子节点为字符串
* 旧子节点没有子节点: 不做事情
* 旧子节点为文本节点: 不做事情
* 旧子节点为数组: 逐个卸载
*/
if (Array.isArray(n1.children)) {
n1.children.forEach(c => unmount(c));
}
hostSetElementText(container, c2);
} else if (Array.isArray(c2)) {
/**
* @description: 分支二: 如果新子节点为数组
* 旧子节点没有子节点: 清空容器
* 旧子节点为文本节点: 清空容器
* 旧子节点为数组: Diff 算法
*/
if (Array.isArray(n1.children)) {
/**
* @description: 新子节点为数组, 旧子节点为数组, 进行 Diff 算法
*/
patchKeyedChildren(c1, c2, container, anchor);
} else {
/**
* @description: 新子节点为数组, 旧子节点为单个节点, 将容器元素清空, 将新子节点数组逐个挂载到容器中
*/
hostSetElementText(container, '');
c2.forEach(c => patch(null, c, container));
}
} else {
/**
* @description: 分支三: 如果新子节点为空
* 旧子节点没有子节点: 不做事情
* 旧子节点为文本节点: 清空容器
* 旧子节点为数组: 逐个卸载
*/
if (Array.isArray(c1)) {
c.forEach(c => unmount(c));
} else if (typeof c1 === 'string') {
hostSetElementText(container, '');
}
}
};

三、patchProps 细节


3.1 patchProp

export function patchProp(el, key, prevValue, nextValue) {
if (key === 'class') {
patchClass(el, nextValue);
} else if (key === 'style') {
patchStyle(el, prevValue, nextValue);
} else if (shouldSetAsProps(el, key, nextValue)) {
// 通过 shouldSetAsProps 判断 key 属性是否是可读属性 或者 存在于 DOM Properties 中
const type = typeof el[key];
if (type === 'boolean' && nextValue === '') {
el[key] = true;
} else {
el[key] = nextValue;
}
} else {
// 如果要设置的属性没有对应的 DOM Properties 或者是可读属性, 则使用 setAttribute 来设置属性
el.setAttribute(key, nextValue);
}
}

3.2 patchProps

patchProps 通过两个循环, 来对属性进行挂载或者更新。

  1. 循环旧属性: 如果旧属性不在新属性中, 去除

    if (oldProps !== EMPTY_OBJ) {
    for (const key in oldProps) {
    if (!(key in newProps)) {
    hostPatchProp(el, key, oldProps[key], null);
    }
    }
    }
  2. 循环新属性: 如果新属性值发生改变, 更新

    for (const key in newProps) {
    const next = newProps[key];
    const prev = oldProps[key];
    if (next !== prev) {
    hostPatchProp(el, key, prev, next);
    }
    }