学习完成响应式系统后,咋们来看看
vue3
组件的初始化流程
既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程
(在app.vue中写入以下内容,来跑流程)
import { h, ref } from "vue";
export default {
name: 'App',
setup() {
const count = ref(0);
return {
count
};
},
render() {
return h('div', { pId: '"helloWorld"' }, [
h('p', {}, 'hello world'),
h('p', {}, `count的值是: ${this.count}`),
])
}
}
咋们在一开始调用createApp会发生什么事情呢?看下图:
在createApp 主要会发生这些事情:
createApp
的时候,在函数内部会调用ensureRenderer()
这个函数ensureRenderer
这个函数中,判断renderer是否存在,不存在则创建,并且传入一系列的api去初始化function ensureRenderer() {
// 如果 renderer 有值的话,那么以后都不会初始化了
return (
renderer ||
(renderer = createRenderer({
// 创建dom节点,div,li,等
createElement,
// 创建文本节点
createText,
// 给文本节点设置文本 node.nodeValue = text
setText,
//给dom设置文本 el.textContent = text;
setElementText,
// 对比更新属性方法
patchProp,
// 节点插入方法 parent.insertBefore(child, anchor)
insert,
// 移除节点 parent.removeChild(child);
remove,
...
}))
);
}
createRenderer
中会直接调用 createAppAPI
这个方法,并且返回一个对象{createApp: createAppAPI(render)}
createAppAPI
中调用 createApp
方法并且拓展了一个mount方法export function createAppAPI(render) {
return function createApp(rootComponent) {
const app = {
_component: rootComponent,
mount(rootContainer) {
const vnode = createVNode(rootComponent);
// 调用 createAPI 里面传入的render方法
render(vnode, rootContainer);
},
};
return app;
};
}
请注意图中有好几处方法的名称都是 createApp, 每一处的createApp的作用是不一样的,这里不是递归调用哦!
具体流程如下图
上图是整个mount阶段和更新阶段都会经过流程了,是不是感觉有一丢丢的小复杂
接下来咋们就单重我们代码的执行角度来分析下mount流程
咋们把所有的update的去掉,他就是mount阶段了。
createApp
的阶段后,咋们就能获取到rootCompontent,rootComponent是整个APP.js
导出的一个对象,rootContainer 则是需要挂载的真实dom节点;createVNode
创建虚拟节点;render(vnode)
;render
中直接调用patch(null, vnode, container)
方法,并传入对应的参数;APP.js
是一个组件,走组件处理逻辑并传入对应的参数processComponent(n1, n2, container, parentComponent)
;n1
为null,那么进入组件的mount逻辑并传入对应的参数mountComponent(n2, container, parentComponent);
;mountComponent
中需要经过三层处理,把组件渲染为 render函数
和对render函数进行依赖收集instance
当中instance
上面绑定propsinstance
上面绑定slotssetupStatefulComponent
给instance
上面绑定其他的内容,并且把组件转成render函数App.js
中是传了 setup函数
的,所以接下来走setup的流程,调用 createSetupContext(instance)
对setup设置上下文 setup && setup(shallowReadonly(instance.props), setupContext)
传入参数执行setup函数并且获得setup函数的执行结果handleSetupResult(instance, setupResult)
根据 setup
函数的返回结果来做对应的事情proxyRefs
的作用就是把 setupResult
对象做一层代理,方便用户直接访问 ref 类型的值APP.js
到目前位置都还没有render函数,调用 finishComponentSetup(instance);
完成组件的setup操作,给实例赋值 render函数在这里咋们可以看到,组件的render函数的优先级是高于template的,只有组件里面没有写render函数,还会去编译模板里面的内容,并且放到组件中作为render函数
7.2.4
主要就是为了给组件实例赋值好render函数,有了render函数,那么就需要来进行依赖收集啦!!! 调用 setupRenderEffect(instance, initialVNode, container)
来进行依赖收集fn
;instance.update = effect(componentUpdateFn, {
scheduler: () => {
// 把 effect 推到微任务的时候在执行
// queueJob(effect);
queueJob(instance.update);
},
});
componentUpdateFn
来挂载组件instance.subTree = normalizeVNode(instance.render.call(proxyToUse, proxyToUse)))
获取子组件beforeMount hook
patch(null, subTree, container, null, instance)
方法来处理子组件processElement(n1, n2, container, anchor, parentComponent);
来处理元素mountElement(n2, container, anchor)
来挂载元素hostCreateElement(vnode.type);
来创建真实的dom mountChildren(vnode.children, el)
则进入到挂载子组件7.2.5.7
操作来传教子组件的真实domhostPatchProp(el, key, null, nextVal);
来给dom绑定值beforemounted 钩子
beforeEnter 钩子
hostInsert(el, container, anchor);
插入真实节点,完成dom的渲染到了这一步,咋们就可以知道vue组件的挂载顺序是 父 before mounted -> before mounted ->子 mounted -> 父mounted类型与koa中间件执行的洋葱模型
mounted 钩子
Entered 钩子
这就是组件初始化的全部流程,有了init的流程后,在后续的更新其实就是差一个 vue diff 算法啦
源代码地址: https://github.com/cll123456/my-study/tree/master/my-vue3-code/2-comp-init