Vue3 功能拆解③ 组件更新机制
文章目录
本系列为 vue-next 源码分析系列的旁系分支,主要目的在于对 vue3 源码中的一些细节进 行分析。本文讲述的是 vue3 中组件的更新机制,比如:属性变更父子组件更新顺序是如 何?。
根据组件的渲染流程,我们知道组件的更新实际是通过 effect 封装了一个 instance.update 函数,当组件状态发生变化时会自动触发这个 update 函数执行,因为这 状态代理属性有收集到这个 update 函数。
instance.update:
instance.update = effect(function componentEffect() {/*...*/})
在 vue-package-reactivity 一节中有更详细的 effect
源码分析。
组件简要渲染,函数执行流程:
精简之后的 instance.update 函数:
|
|
主要分为 mount 和 update 两部分(if…else)
mount: beforeMount hook -> onVnodeBefoureMount -> renderComponentRoot subTree -> patch subTree -> mounted hook -> onVnodeMounted -> [ activated hook ]
update: next ? -> beforeUpdate hook -> onVnodeBeforeUpdate -> renderComponentRoot nextTree -> patch -> updated hook -> onVnodeUpdated
两个阶段中,有一个相关联的部分, subTree <-> nextTree 等于一个是 old tree 一个是
new tree, mount 阶段 patch(null, subTree)
update 阶段 patch(subTree, nextTree)
tree 的产生一样来自同一个函数:
mount: renderComponentRoot(instance)
update: renderComponentRoot(instance)
这个函数里面会去执行 instance 的 render 函数得到最新的 vnode tree ,等于是状态更 新触发这个函数去执行 render 得到最新的组件 vnode truee。
render 函数来源:如果是函数组件就是该函数本身(instance.type
),如果是对象组件则
是对象内部的 instance.render
函数(可能来自 setup 返回的函数)。
测试(/js/vue/tests/L3jBmxJfNN.js):父子组件更新顺序
上面链接可以查看测试源码。
这里我们在父子组件中均增加组件更新 hook:
|
|
点击按钮可以改变父子组件颜色,查看输出结果,会发现
只更新父组件背景色,只会触发 parent log
只更新子组件背景色,只会触发 child log
更新父组件背景色,同时改变父组件中传递给子组件的属性
子组件 style.backgroud 属性绑定 bgcolor,该值来自 parent 传递进来的 attrs,这 里为何是
attrs
而不是props
?1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
function changeParentColorWithProp() { changeParentColor(); bgcolor.value = bgcolor.value === "black" ? "coral" : "black"; } const Child = defineComponent({ setup() { onUpdated(() => log("child updated")); onBeforeUpdate(() => log("child before update")); }, render() { const { bgcolor } = this.$attrs; return h( "p", { style: { background: bgcolor.value || childBgColor.value, }, onVnodeUpdated(newVnode, oldVnode) { log( "child vnode updated, new: " + newVnode.props.style.background + ", old: " + oldVnode.props.style.background ); }, }, "我是子组件" ); }, });