「这是我参与2022首次更文挑战的第18天,活动详情查看:2022首次更文挑战」
先上函数执行栈图, 如图所示,是一次mount函数执行时的函数调用栈,出现在stack里的函数都是正在执行还没结束的,结束的已经出栈了。 从栈底到栈顶,就是这一过程中函数的执行顺序。
然后是一张流程图。我们从mount 开始。
mount
mount只会执行一次,在执行过程中,我们暂且不关注ssr 所有hydrate的判断走false就行了。 在mount里,创建了根组件vNode,然后执行了render ,变更状态为已加载,,最后返回vNode.component 的代理, 也就是组件实例的代理。
render
于是,我们自然而然的进入了render函数,并且携带了刚创建好的根组件的Vnode、容器、是否svg。我们暂时也不也不考虑svg元素,后续所有的isSvg都为false。
在render函数中,没有老的vnode,我们就自然而然要执行patch,然后执行某个回调,最后记录当前vnode(下一次的老值)。
patch
第一次执行patch的时候,我们的type是一个对象,故而在switch那里一定会走default,然后的判断里,我们的shapeFlag 是4 ,看了一下声明 只有6 和它做位与运算不是0。结果就是执行 processComponent。
processComponent
在这个函数里,第一次执行时,没有老值,也没有什么keepalive,因此就会执行mountComponent
mountComponent
这个函数里,那一堆开发环境才执行的逻辑都不看, 也就径直来到了setupRenderEffect
setupRenderEffect
断点执行到这一步,很容易断掉。 这个函数主要就是声明了一个componentUpdate函数,看它的命名就知道是组件更新时调用。然后使用ReactiveEffect注册了一个监控,也就是在重新渲染时就会执行。 注册完这个effect之后, 它又重新声明了一个upadate改变了effect.run (差不多可以认为是注册的回调)的this指向,最后执行它。
执行这个update,就是执行了effect.run. 然后,你又发现这个run最后是一个尾调用effect.fn(). 这个fn就是之前注册的回调函数 componentUpdate。
componentUpdate
于是乎,我们回过头来看这个函数,差点就放跑了。断点调试不要看到return就直接放跑了,兴许是函数调用呢。此时,mount仍未执行完,忽略ssr, 那就剩余两行代码
const subTree = (instance.subTree = renderComponentRoot(instance)) patch( null, subTree, container, anchor, instance, parentSuspense, isSVG )
也就是说我们再一次进入了patch,不过不同的是,这次的type是对应的元素, 后面执行的是processElement.
processElement 里面就会执行 mountElement, mountElement里面就会将vnode对应的dom构建出来并追加到dom中。 如果这个元素有子节点,就会执行mountChildren, mountChildren 其实就是遍历执行mountComponent。 到这里就是继续递归了。
等递归结束,回溯,结束,mount就完成了。