更新element的children
- 实现的功能:
- 我们更新为 Text,原来的节点是Array
- 我们更新为 Text,原来的节点是Text
- 我们更新为 Array, 原来的节点是Text
- 我们更新为 Array, 原来的节点是Array
- 初始化的文件,我们在 this 上挂载一个变量布尔值,后续通过控制台动态修改该变量的值,来实现更新功能
- index.html
- main.js
- App.js
js
复制代码
// App.js
import {
h,
ref
} from "../../lib/guide-mini-vue.esm.js";
import {
ArrayToText
} from "./ArrayToText.js";
export const App = {
name: "App",
render() {
return h("div", {},
[
h('p', {}, '主页'),
h(ArrayToText)
]
)
},
setup() {
return {}
},
}
js
复制代码
// ArrayToText.js
import {
h,
ref
} from '../../lib/guide-mini-vue.esm.js'
const nextChildren = "newChildren"
const prevChildren = [h('div', {}, "A"), h('div', {}, "B")]
export const ArrayToText = {
name: "ArrayToText",
setup() {
const isChange = ref(false); // 挂载布尔值
window.isChange = isChange;
return {
isChange,
}
},
render() {
const self = this;
return self.isChange === true // 使用布尔值来进行条件判断,方便控制台控制,进行更新为 text
?
h("div", {}, nextChildren) : h("div", {}, prevChildren);
}
}
js
复制代码
// TextToArray.js
import {
h,
ref
} from '../../lib/guide-mini-vue.esm.js'
const prevChildren = "oldChildren"
const nextChildren = [h('div', {}, "A"), h('div', {}, "B")]
export const TextToArray = {
name: "ArrayToText",
setup() {
const isChange = ref(false);
window.isChange = isChange;
return {
isChange,
}
},
render() {
const self = this;
return self.isChange === true ?
h("div", {}, nextChildren) : h("div", {}, prevChildren);
}
}
js
复制代码
import {
h,
ref
} from '../../lib/guide-mini-vue.esm.js'
const nextChildren = "newChildren"
const prevChildren = "oldChildren"
export const TextToText = {
name: "TextToText",
setup() {
const isChange = ref(false);
window.isChange = isChange;
return {
isChange,
}
},
render() {
const self = this;
return self.isChange === true ?
h("div", {}, nextChildren) : h("div", {}, prevChildren);
}
}
- 实现更新 我们更新为 Text,原来的节点是Array
js
复制代码
// renderer.ts
// 处理元素节点:如果 n1 不存在则说明是初次挂载(mount),否则为更新(patch)
// parentComponent 用于在挂载子节点时传递上下文(例如组件实例)
function processElement(n1, n2, container, parentComponent) {
if (!n1) {
// 这里进行初次挂载,将 n2 渲染为真实 DOM 并插入 container
mountElement(n2, container, parentComponent)
} else {
// 已存在则执行补丁流程,将 n1(旧 vnode)更新为 n2(新 vnode)
patchElement(n1, n2, container)
}
}
function patchElement(n1, n2, container) {
// 提取旧 props 和 新 props,保证为对象(避免 undefined)
let oldProps = n1.props || EMPTY_OBJECT
let newProps = n2.props || EMPTY_OBJECT
// 复用旧 vnode 的真实元素 el 给新 vnode,后续直接在 el 上进行更新
let el = n2.el = n1.el
// 先更新 children 再更新 props(顺序可以影响 DOM 操作和性能)
patchChildren(n1, n2, el) // ✅
// 最后对 props 进行差异更新
patchProps(el, oldProps, newProps)
}
function patchChildren(n1, n2, container) { // ✅
// 处理 children 的更新:根据 shapeFlag 判断之前/之后 children 的类型(文本或数组)
const {
shapeFlag: prevShapeFlag,
} = n1
const {
shapeFlag: nextShapeFlag,
children: c2
} = n2
// 情况:旧的是数组,新的是文本(Array -> Text)
if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
// 卸载旧的数组子节点(移除对应 DOM)
unmountChildren(n1.children)
// 将容器设置为新的文本内容
hostSetElementText(container, c2)
}
}
}
function unmountChildren(children) { // ✅
// 卸载一组子节点:遍历 children,调用宿主平台的删除方法移除对应的真实元素
children.forEach(child => {
const el = child.el
hostPatchRemove(el) // ✅
})
}
- 我们更新为 Text,原来的节点是Text,
js
复制代码
// renderer.ts
// 情况:新的是文本(Text)
function patchChildren(n1, n2, container) {
const {
shapeFlag: prevShapeFlag,
children: c1
} = n1
const {
shapeFlag: nextShapeFlag,
children: c2
} = n2
if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
// 如果旧的是数组,需要先卸载数组子节点再设置文本
unmountChildren(n1.children)
hostSetElementText(container, c2)
} else { // ✅ 旧的也是文本的情况
// 只有在新旧文本内容不一致时,才替换元素文本,避免不必要的 DOM 操作
if (c1 !== c2) {
hostSetElementText(container, c2)
}
}
}
}
// 上面代码的优化版本:将重复逻辑拆成更通用的步骤,先处理数组->其他情况的卸载,再统一处理文本替换
function patchChildren(n1, n2, container, parentComponent) {
const {
shapeFlag: prevShapeFlag,
children: c1
} = n1
const {
shapeFlag: nextShapeFlag,
children: c2
} = n2
if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
// 如果之前是数组,则先卸载旧数组子节点
if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
unmountChildren(n1.children)
}
// 只有新旧文本不同,才进行文本替换,减少 DOM 操作
if (c1 !== c2) {
hostSetElementText(container, c2)
}
}
}
- 我们更新为 Array, 原来的节点是Text
js
复制代码
// renderer.ts
function patchChildren(n1, n2, container, parentComponent) {
const {
shapeFlag: prevShapeFlag,
children: c1
} = n1
const {
shapeFlag: nextShapeFlag,
children: c2
} = n2
if (nextShapeFlag & shapeFlags.TEXT_CHILDREN) {
// 新的是文本的情况(Text)
if (prevShapeFlag & shapeFlags.ARRAY_CHILDREN) {
// 旧的是数组 -> 卸载旧子节点
unmountChildren(n1.children)
}
if (c1 !== c2) {
// 新旧文本内容不同时替换文本
hostSetElementText(container, c2)
}
} else {
// ✅ 新的是数组类型(Array)
if (prevShapeFlag & shapeFlags.TEXT_CHILDREN) {
// 旧的是文本 -> 先清空容器的文本,再挂载新的数组子节点
hostSetElementText(container, "")
mountChildren(c2, container, parentComponent)
} else {
// 旧的也是数组:这里应当进行更复杂的数组 diff 和更新逻辑(后续实现)
}
}
}
- 我们更新为 Array, 原来的节点是Array,对比比较复杂,使用到了 diff 算法,下一节更新