已经知道了早初始化过程中,在 update 方法里面间接调用了___patch__去更新,那么在今后的更新过程中,新老 Vnode 都存在,那么这些新老的 Vnode 是从哪里来的呢?
来看一下 update 的源码
Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
...
const prevVnode = vm._vnode
vm._vnode = vnode
...
vm.$el = vm.__patch__(prevVnode, vnode)
}
可以看到,在更新时,把当前的的 vnode 保存了一份,放在了 preVnode 中。这个就是老的 Vnode,而新的 vnode 是代入的参数
那么新的 Vnode 从哪里来的呢?需不需要再从头开始走一个编译的过程?这样是不是有些耗费时间了?
看一下 Vue 中的解决方案
...
if (process.env.NODE_ENV !== 'production' && config.performance && mark) {
updateComponent = () => {
...
const vnode = vm._render()
...
vm._update(vnode, hydrating)
}
}
...
可以看到即将要带入到_update中参与更新的参数,是我们调用_render 方法生成的,这个就是新的 vnode。那么这个_render 方法又是什么?
我们找到 instance/render.js
Vue.prototype._render = function (): VNode {
...
try {
vnode = render.call(vm._renderProxy, vm.$createElement)
} catch (e) {}
...
return vnode
}
- 可以看到,不管是新的还是旧的 vnode,vnode 总是有代入参数的 render 方法生成,也就是由编译完成的代码字符串代入 new Function()中生成的函数。
- render 的前身是代码字符串,会有_c,_v 这些标识。
- 我们常挂在嘴边的渲染函数,就是这个 creatElement。
- 将代码字符串代入这个函数creatElement,会调用_c,_v ,_l,_f, _m 等方法去生成 vnode。
creatElement 方法:
export function createElement (
context: Component,
tag: any,
data: any,
children: any,
normalizationType: any,
alwaysNormalize: boolean
): VNode | Array<VNode> {
return _createElement(context, tag, data, children, normalizationType)
}
这个方法最终返回了 Vnode。我们并没有经过组件的编译。说明直接书写渲染函数便可以生成 Vnode,可以节省下来组件编译所应用的时间
当有了新老 Vnode 的时候,我们就可以对这两个新老 vnode 进行 diff和path并进行更新了。接下来会分析 vnode,diff 和 patch 的源码