vue首次挂载流程
vue首次挂载流程
从vue.createApp开始
从vue.createApp开始,在runtime-dom/index.ts中的createApp方法,具体实现在runtime-core\src\renderer.ts的baseCreateRenderer
baseCreateRenderer返回create函数,值为createAppAPI返回值。
createAppAPI返回的值就是app实例,包含mixin、use、mount等方法。
参数归一化
背景:app.mout(‘#app’),这个函数是可以接受字符串和dom元素作为容器,是如何处理字符串的呢?
runtime-dom/index.ts中的createApp:
通过解构上述createAppAPI返回app对象获取到mount方法,重新将参数做处理,当为字符串时:
1 | if (isString(container)) { |
模板归一化
背景:编写vue模板代码有两种方式
当没有reder方法和template对象时,vue会自动添加一个template对象,值为模板内容:
1 | if (!isFunction(component) && !component.render && !component.template) { |
instance实例
在mountComponent中,通过createComponentInstance方法创建一个空的实例,然后在setupComponent中添加属性,具体添加过程是在setupStatefulComponent和finishComponentSetup添加的
compile编译(获得render方法)
在finishComponentSetup方法中,通过compile方法编译template获得render方法
1 | Component.render = compile(template, finalCompilerOptions) |
packages\compiler-core\src\compile.ts:
这里就是具体实现了
1 | const ast = isString(source) ? baseParse(source, resolvedOptions) : source |
通过baseParse函数将template转换为ast抽象语法树,再进行转换。为什么要转换呢?
1 | transform( |
这里就会有一个优化了,叫做静态提升,指的是 Vue 编译器识别模板中的静态内容(不依赖动态数据的部分),并将这些内容”提升”到渲染函数之外。这样这些静态内容只会被创建一次,而不是在每次组件重新渲染时都重新创建。
subTree
通过执行render函数获取到VNode
1 | const subTree = (*instance*.subTree = renderComponentRoot(*instance*)) |
patch
将subTree通过patch挂载到container上。
1 | patch( |
patch主要的参数:第一个是旧VNode,第二个是新VNode
挂载
通过调用hostCreateElement创建DOM,再通过hostInsert将创建的元素插入到container中
更新DOM(diff算法)
无key更新(patchUnkeyedChildren)
函数会接受两个新旧VNode数组,获取到新旧VNode的长度,取较小值开始循环。每次循环将从第一个新旧VNode开始patch。循环结束后,比较新旧VNode的长度,oldLength > newLength则调用unmountChildren删除不需要渲染的节点;反之,则添加mountChildren添加新DOM
有key更新(patchKeyedChildren)
注意五个变量:
- i:下标,从0开始
- e1:旧VNode最后一个元素下标
- e2:新VNode最后一个元素下标
- s1:旧VNode中未匹配部分的起始索引,s1 = i
- s2:新VNode中未匹配部分的起始索引,s2 = i
比较节点(isSameVNodeType:type和key相同,返回true),有以下几种情况:
从新旧VNode的下标i开始比较,相同则patch,i++,继续循环;不同则break
从新旧VNode的最后一个元素开始比较,相同则patch,e1–,e2–,继续循环;不同则break
i > e1 && i <=e2,那么从i到e2的节点都是新增,循环patch
i > e2 && i <=e1,那么从i到e1的节点都是删除的,循环unmount
复杂情况:
// [i … e1 + 1]: a b [c d e] f g
// [i … e2 + 1]: a b [e d c h] f g
// i = 2, e1 = 4, e2 = 5
// s1 = 2, s2 = 2
- e2与s1对比,若它们的key和type都相同,把s1对应的真实节点移动到e1对应的真实节点的后面,并且s1变成undefined。s1++,e2–
- e1与s2对比,若它们的key和type都相同,把e1对应的真实节点移动到s1对应的真实节点的前面,并且e1变成undefined。s2++,e1–