更新的逻辑
- 如果前后元素不一致, 删除老的 添加新的
- 老的和新的一样,复用。属性可能不一样,比对属性更新属性
- 比对子集
初始化和diff算法都在 上一章:第八节 实现的
path
方法
前后元素不一致
示例
render(h('h1',{style: {'color': 'red'},onClick: ()=>{alert(1)}},h('span','lyp'),'hello'),app)
setTimeout(()=>{
render(h(Text, '111'),app)
},1000)
实现
// 是否是相同虚拟节点
export const isSameVNodeType = (n1, n2) => {
// 判断两个虚拟节点是否是同一节点:1、标签名一致 2、key是一样的
return n1.type === n2.type && n1.key === n2.key;
}
const patch = (n1,n2,container) => {
// 初始化和diff算法都在path方法
// 没有更新
if(n1 === n2) return
// 1、有n1, n1和n2不是同一个节点, 卸载老的再添加新的
if(n1 && !isSameVNodeType(n1,n2)){
unmount(n1) // 删除老的
n1 = null // 下边processText 会走创建
}
// 上一节实现过的
const {type, shapeFlag} = n2
switch(type){ // 如果是文本直接插入
case Text:
processText(n1,n2,container);
break;
default:
// 是元素的话 渲染元素
if(shapeFlag & ShapeFlags.ELEMENT){
processElement(n1, n2, container)
}
}
}
前后元素一致
文本更新
示例
render(h(Text, '111'),app)
setTimeout(()=>{
render(h(Text, '222'),app)
},1000)
实现
// 处理文本
const processText= (n1,n2,container) => {
if(n1 === null){
// 初始化流程
n2.el = hostCreateText(n2.children)
hostInsert(n2.el, container)
}else {
// 文本更新, 可以直接复用老的元素
const el = n2.el = n1.el //n1.el 赋值给 n2.el
if(n1.children !== n2.children){
hostSetText(el, n2.children) // 文本的更新
}
}
}
比较两个元素的属性和孩子节点
示例
render(h('h1',{style: {'color': 'red'}}, 'lyp'),app)
setTimeout(()=>{
render(h('h1',{style: {'color': 'blue',background: 'red'}}, 'lyp'),app)
},1000)
入口
const patchElement = (n1, n2) => {
let el = (n2.el = n1.el);
const oldProps = n1.props || {};
const newProps = n2.props || {};
patchProps(oldProps, newProps, el); // 比对新老属性
patchChildren(n1, n2, el); // 比较元素的孩子节点
}
const processElement = (n1, n2, container) => {
if (n1 == null) {
mountElement(n2, container)
} else {
patchElement(n1, n2); // 比较两个元素
}
}
属性比对实现
// 3、比对属性
const patchProps = (oldProps, newProps, el) => {
for(let key in newProps){ // 新的里边有直接用新的盖掉就好
hostPatchProp(el,key,oldProps[key], newProps[key])
}
for(let key in oldProps){ // 如果老的里边有新的里边没有 把老的删掉
if(newProps[key] === undefined){
hostPatchProp(el,key,oldProps[key], undefined) // null没有被认为是空
}
}
}
子元素比对实现
子元素 可能是 文本、null、数组
子元素比较情况
新儿子 | 旧儿子 | 操作方式 |
---|---|---|
文本 | 数组 | (删除老儿子,设置文本内容) |
文本 | 文本 | (更新文本即可) |
文本 | 空 | (更新文本即可) 与上面的类似 |
数组 | 数组 | (diff算法) |
数组 | 文本 | (清空文本,进行挂载) |
数组 | 空 | (进行挂载) 与上面的类似 |
空 | 数组 | (删除所有儿子) |
空 | 文本 | (清空文本) |
空 | 空 | (无需处理) |
使用
// 文本 数组
render(h('h1',{style: {'color': 'red'}}, '333'),app)
setTimeout(()=>{
render(h('h1',{style: {'color': 'blue',background: 'red'}}, ['111', '222']),app)
},1000)
// 文本 文本
render(h('h1',{style: {'color': 'red'}}, 'lyp'),app)
setTimeout(()=>{
render(h('h1',{style: {'color': 'blue',background: 'red'}}, 'jdlyp'),app)
},1000)
// 文本 空
render(h('h1',{style: {'color': 'red'}}, 'lyp'),app)
setTimeout(()=>{
render(h('h1',{style: {'color': 'blue',background: 'red'}}),app)
},1000)
// 数组 文本
render(h('h1',{style: {'color': 'red'}}, ['111', '222']),app)
setTimeout(()=>{
render(h('h1',{style: {'color': 'blue',background: 'red'}}, '333'),app)
},1000)
render(h('h1',{style: {'color': 'red'}}),app)
setTimeout(()=>{
render(h('h1',{style: {'color': 'blue',background: 'red'}}, h('span', 'jdlyp')),app)
},1000)
实现
- 1、新节点是 文本
- 2、新的 是数组
- 3、新的既不是文本 也不是 数组,就剩下一种情况为空
// 删除所有子节点
const unmountChildren = (children) =>{
for(let i = 0 ; i < children.length; i++){
unmount(children[i]);
}
}
// 比较元素的子节点
const patchChildren = (n1, n2, el) => {
const c1 = n1 && n1.children
const c2 = n2 && n2.children
// 可能是 文本、null、数组
// 比较两个 儿子节点的 差异
// 文本 数组 (删除老儿子,设置文本内容)
// 文本 文本 (更新文本即可)
// 数组 数组 (diff算法)
// 数组 文本 (清空文本,进行挂载)
// 空 数组 (删除所有儿子)
// 空 文本 (清空文本)
const prevShapeFlag = n1.shapeFlag;
const shapeFlag = n2.shapeFlag;
if(shapeFlag & ShapeFlags.TEXT_CHILDREN){ // 1、新节点是 文本
if(prevShapeFlag & ShapeFlags.ARRAY_CHILDREN){ //文本 数组
unmountChildren(c1); // 删除老的所有子节点
}
if(c1 !== c2){ // 文本 文本 设置文本(包括 文本 空)
hostSetElementText(el,c2);
}
}else {
if(prevShapeFlag & ShapeFlags.ARRAY_CHILDREN){ // 2、新的 是数组
if(shapeFlag & ShapeFlags.ARRAY_CHILDREN){ // 数组 数组
// diff算法
}else{
unmountChildren(c1); // 数组 文本和null 、老的不是数组 删除以前的所有子节点
}
}else{ // 3、新的既不是文本 也不是 数组,就剩下一种情况为空
if(prevShapeFlag & ShapeFlags.TEXT_CHILDREN){ // 3.1、 老的是 文本
hostSetElementText(el,''); // 清空 文本
}
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 3.2 老的是数组
mountChildren(c2, el); // 清空子节点
}
}
}
}
下一章我们实现 新旧子集都是数组的 diff算法