[Vue 源码] Vue 3.2 - 组件更新原理
代码运行结果
代码示例
<body>
<script src="../dist/runtime-dom.global.js"></script>
<div id="app"></div>
<script>
const { createApp, h, reactive } = VueRuntimeDOM;
const Son = {
props: {
name: String,
},
render() {
return h("p", this.name);
},
};
const App = {
setup() {
const state = reactive({ name: "zf" });
setTimeout(() => {
state.name = "jw";
}, 3000);
return () => {
return h(Son, { name: state.name });
};
},
};
createApp(App).mount("#app");
</script>
</body>
挂载阶段
第一步,执行第一句代码,createApp(App).mount("#app")
; 进行组件挂载。可以查看之前的文章,来看详细流程。
我们这里只聊聊组件挂载的核心,详细挂载请看: Vue3 组件挂载原理,执行 setup 函数。
- 对于 App 组件来讲,挂载时的核心就是会创建组件的 ReactiveEffect 对象。
- 每次更新和挂载都会调度
instance.update()
也就是 componentUpdateFn 函数。 - 并且将将返回的函数作为 render 函数,进行调用得到组件的虚拟 Dom 节点,来作为 instance.subtree 属性。
- Son 组件同理。
第二步:render 函数中 state.name 触发,触发 state 响应式对象的的 getter 操作,收集 组件的 ReaciveEffect 对象作为响应式对象的依赖。如下结构:
{
{ name: "zf" }: {
name : [组件的 ReaciveEffect, 其他的 ReaciveEffect(比如 watch)]
}
}
挂载核心操作完毕。
更新阶段
执行第一句代码:state.name = "jw"
第一:state.name = "jw"
, 原理可以看这篇文章:Vue 3.2 - Reactive 原理,这里几句带过,触发响应式对象 state 的 setter 操作,通过 Reflect.set 方法更新属性,然后触发 trigger 操作,执行 name 属性的 RectiveEffects 对象的 fn 属性,也就是 componentUpdateFn 函数。
第二:在 componentUpdateFn 函数中,调用 renderComponentRoot 函数,该函数中,再次调用 instance.render.call(), 获得新的虚拟 Dom, 再去将旧的和新的虚拟 Dom 进行 patch。
const nextTree = renderComponentRoot(instance)
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
第三:由于本次是更新逻辑,所以来到了 updateComponent 方法,在 updateComponent 调用 Son 组件 instace.update(),也就是 componentUpdateFn 函数。 在该函数中调用了 Son 组件 instance.render 方法,拿到最新的虚拟 Dom 进行渲染。
if (shouldUpdateComponent(n1, n2, optimized)) {
instance.next = n2
invalidateJob(instance.update)
instance.update()
}
this 访问的时候,实际上是对 instance 上属性的代理。这里是可以代理的属性,请大家自行参考。
转载自:https://juejin.cn/post/7211033231058010170