更新对比
children更新主要考虑一下3种情况,其中array->array是最复杂的,这章节先做其他两种情况
text
->array
array
->array
array
->text
例子
App.js
/*
* @Author: Lin ZeFan
* @Date: 2022-04-04 11:44:08
* @LastEditTime: 2022-04-04 11:46:34
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\example\patchChildren\App.js
*
*/
import { h } from "../../lib/mini-vue.esm.js";
import ArrayToText from "./ArrayToText.js";
import TextToNewText from "./TextToNewText.js";
import TextToArray from "./TextToArray.js";
import ArrayToArray from "./ArrayToArray.js";
export default {
setup() {
return {};
},
render() {
return h("div", {}, [
h("div", {}, "3种情况变化"),
// h(ArrayToText),
// h(TextToNewText),
// h(TextToArray),
h(ArrayToArray),
]);
},
};
main.js
/*
* @Author: Lin ZeFan
* @Date: 2022-04-04 11:51:55
* @LastEditTime: 2022-04-04 11:51:56
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\example\patchChildren\main.js
*
*/
import App from './App.js'
import { createApp } from '../../lib/mini-vue.esm.js'
createApp(App).mount('#app')
ArrayToText.js
/*
* @Author: Lin ZeFan
* @Date: 2022-04-04 11:44:36
* @LastEditTime: 2022-04-04 11:46:23
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\example\patchChildren\ArrayToText.js
*
*/
import { h, ref } from '../../lib/mini-vue.esm.js'
export default {
setup() {
const isChange = ref(false)
window.isChange = isChange
return {
isChange,
}
},
render() {
return h(
'div',
{},
this.isChange
? 'newChildren'
: [h('div', {}, 'oldChildren1'), h('div', {}, 'oldChildren1')]
)
},
}
TextToArray.js
/*
* @Author: Lin ZeFan
* @Date: 2022-04-04 11:44:45
* @LastEditTime: 2022-04-04 11:46:48
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\example\patchChildren\TextToArray.js
*
*/
import { h, ref } from '../../lib/mini-vue.esm.js'
export default {
setup() {
const isChange = ref(false)
window.isChange = isChange
return {
isChange,
}
},
render() {
return h(
'div',
{},
this.isChange
? [h('div', {}, 'newChildren1'), h('div', {}, 'newChildren2')]
: 'oldChildren'
)
},
}
TextToNewText.js
/*
* @Author: Lin ZeFan
* @Date: 2022-04-04 11:44:41
* @LastEditTime: 2022-04-04 11:47:02
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\example\patchChildren\TextToNewText.js
*
*/
import { h, ref } from '../../lib/mini-vue.esm.js'
export default {
setup() {
const isChange = ref(false)
window.isChange = isChange
return {
isChange,
}
},
render() {
return h('div', {}, this.isChange ? 'newChildren' : 'oldChildren')
},
}
ArrayToArray.js
/*
* @Author: Lin ZeFan
* @Date: 2022-04-04 11:44:52
* @LastEditTime: 2022-04-04 11:44:52
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\example\patchChildren\ArrayToArray.js
*
*/
// 老的是 array
// 新的是 array
import { ref, h } from "../../lib/mini-vue.esm.js";
// 1. 左侧的对比
// (a b) c
// (a b) d e
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// ];
// const nextChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "D" }, "D"),
// h("p", { key: "E" }, "E"),
// ];
// 2. 右侧的对比
// a (b c)
// d e (b c)
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// ];
// const nextChildren = [
// h("p", { key: "D" }, "D"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// ];
// 3. 新的比老的长
// 创建新的
// 左侧
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
// const nextChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// ];
// 右侧
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
// const prevChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
// const nextChildren = [
// h("p", { key: "C" }, "C"),
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// ];
// 4. 老的比新的长
// 删除老的
// 左侧
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// ];
// const nextChildren = [h("p", { key: "A" }, "A"), h("p", { key: "B" }, "B")];
// 右侧
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// ];
// const nextChildren = [h("p", { key: "B" }, "B"), h("p", { key: "C" }, "C")];
// 5. 对比中间的部分
// 删除老的 (在老的里面存在,新的里面不存在)
// 5.1
// a,b,(c,d),f,g
// a,b,(e,c),f,g
// D 节点在新的里面是没有的 - 需要删除掉
// C 节点 props 也发生了变化
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C", id: "c-prev" }, "C"),
// h("p", { key: "D" }, "D"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// const nextChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "C", id: "c-next" }, "C"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// 5.1.1
// a,b,(c,e,d),f,g
// a,b,(e,c),f,g
// 中间部分,老的比新的多, 那么多出来的直接就可以被干掉(优化删除逻辑)
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C", id: "c-prev" }, "C"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "D" }, "D"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// const nextChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "C", id:"c-next" }, "C"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// 2 移动 (节点存在于新的和老的里面,但是位置变了)
// 2.1
// a,b,(c,d,e),f,g
// a,b,(e,c,d),f,g
// 最长子序列: [1,2]
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// h("p", { key: "D" }, "D"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// const nextChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "C" }, "C"),
// h("p", { key: "D" }, "D"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// 2.2
// a,b,(c,d,e,z),f,g
// a,b,(d,c,y,e),f,g
// 最长子序列: [1,3]
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// h("p", { key: "D" }, "D"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "Z" }, "Z"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// const nextChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "D" }, "D"),
// h("p", { key: "C" }, "C"),
// h("p", { key: "Y" }, "Y"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// 3. 创建新的节点
// a,b,(c,e),f,g
// a,b,(e,c,d),f,g
// d 节点在老的节点中不存在,新的里面存在,所以需要创建
// const prevChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "C" }, "C"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
// const nextChildren = [
// h("p", { key: "A" }, "A"),
// h("p", { key: "B" }, "B"),
// h("p", { key: "E" }, "E"),
// h("p", { key: "C" }, "C"),
// h("p", { key: "D" }, "D"),
// h("p", { key: "F" }, "F"),
// h("p", { key: "G" }, "G"),
// ];
export default {
name: "ArrayToArray",
setup() {
const isChange = ref(false);
window.isChange = isChange;
return {
isChange,
};
},
render() {
const self = this;
return h("div", {}, self.isChange === true ? nextChildren : prevChildren);
},
};
实现
text 转 text
新老children都是text,直接设置元素的text即可
新增一个 setElementText 方法
runtime-core\render.ts
export function createRenderer(options) {
// 改名字是为了 debug 方便
const {
createElement: hostCreateElement,
insert: hostInsert,
patchProp: hostPatchProp,
selector: hostSelector,
setElementText: hostSetElementText,
} = options;
// other code
runtime-dom\index.ts
export function setElementText(el, text) {
el.textContent = text;
}
const renderer: any = createRenderer({
createElement,
patchProp,
insert,
selector,
setElementText,
});
判断children类型
function patchChildren(
n1: any,
n2: any,
container: any,
parentComponent: any
) {
const { el, children: n1Children } = n1;
const { children: n2Children } = n2;
const n1ShapeFlags = getChildrenShapeFlags(n1Children);
const n2ShapeFlags = getChildrenShapeFlags(n2Children);
if (n1ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
if (n2ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
// text -> text
// 直接覆盖值
hostSetElementText(el, n2Children);
}
}
}
text 转 array
新的children是array的话,把老的text先清空,再把新的array循环push进去
function patchChildren(
n1: any,
n2: any,
container: any,
parentComponent: any
) {
const { el, children: n1Children } = n1;
const { children: n2Children } = n2;
const n1ShapeFlags = getChildrenShapeFlags(n1Children);
const n2ShapeFlags = getChildrenShapeFlags(n2Children);
if (n1ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
if (n2ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
// text -> text
// 直接覆盖值
hostSetElementText(el, n2Children);
} else {
/** text -> array
* 1. 先清空原先的text
* 2. 再push进新的children
*/
hostSetElementText(el, "");
mountChildren(n2Children, el, parentComponent);
}
}
array 转 text
先做子元素删除,再设置textContent
新增remove函数
runtime-dom\index.ts
export function remove(child) {
const parentNode = child.parentNode;
parentNode && parentNode.removeChild(child);
}
const renderer: any = createRenderer({
createElement,
patchProp,
insert,
selector,
setElementText,
remove,
});
循环删除子元素
runtime-core\render.ts
function patchChildren(
n1: any,
n2: any,
container: any,
parentComponent: any
) {
const { el, children: n1Children } = n1;
const { children: n2Children } = n2;
const n1ShapeFlags = getChildrenShapeFlags(n1Children);
const n2ShapeFlags = getChildrenShapeFlags(n2Children);
if (n1ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
if (n2ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
// text -> text
// 直接覆盖值
hostSetElementText(el, n2Children);
} else {
/** text -> array
* 1. 先清空原先的text
* 2. 再push进新的children
*/
hostSetElementText(el, "");
mountChildren(n2Children, el, parentComponent);
}
} else {
if (n2ShapeFlags === ShapeFlags.TEXT_CHILDREN) {
/** array -> text
* 1. 删除子元素
* 2. 重新赋值
*/
unmountChildren(n1Children);
hostSetElementText(el, n2Children);
}
}
}
function unmountChildren(child) {
for (let index = 0; index < child.length; index++) {
// 是一个h对象,当前dom元素存在el
const element = child[index] && child[index].el;
hostRemove(element);
}
}