五 Vue 原理解析之 虚拟Dom 到真实Dom的转换过程

上一篇 vue 原理解析(四): 虚拟Dom 是怎么生成的
再有一颗树形结构的对象后,我们需要做的就是讲这棵树跟真实Dom树形成映射关系 。我们先回顾之前的 方法:
export function mountComponent(vm, el) {vm.$el = el...callHook(vm, 'beforeMount')...const updateComponent = function () {vm._update(vm._render())}...}
我们已经执行完了vm. 方法拿到了VNode, 现在将它作为参数传给vm. 方法并执行 。vm.这个方法的作用就是将VNode 转为真实的Dom, 不过它有两个执行时机:
首次渲染
更新页面
先来看看vm.方法的定义:
Vue.prototype._update = function(vnode) {... 首次渲染vm.$el = vm.__patch__(vm.$el, vnode)// 覆盖原来的vm.$el...}
这里的 vm. e l 是 之 前 在 = = m o u n t C o m p o n e n t = = 方 法 内 就 挂 载 的,一 个 真 实 的 = = D o m = = 元 素。首 次 渲 染 会 传 入 v m . el 是之前在 ==== 方法内就挂载的,一个真实的==Dom==元素 。首次渲染会传入 vm. el是之前在====方法内就挂载的,一个真实的==Dom==元素 。首次渲染会传入vm.el 以及得到的VNode, 所以看下vm.patch 定义:
Vue.prototype.__patch__ = createPatchFunction({ nodeOps, modules })
patch 是方法内部返回的一个方法,它接受一个对象:
属性:封装了操作原生Dom 的一些方法的集合,如:创建、插入,移除这些,我们到使用的地方咋详解 。
属性: 创建真实Dom 也需要生成它的如class/attrs/style 等属性 。是一个数组集合,数组的每一项都是这些属性对应的钩子方法,这些属性的创建,更新,销毁等都有对应钩子方法 。当某一时刻需要做某件事,执行对应的钩子即可 。比如它们都有 这个钩子方法,如将这些 钩子收集到一个数组内,需要在真实Dom上创建这些属性时,依次执行数组的每一项,也就是依次创建了它们 。
PS: 这里 属性内的钩子方法是区分平台的,web, weex 以及 SSR 它们调用VNode 方法方式并不相同,所以vue在这里又使用了函数柯里化这个骚操作,在 内将平台的差异化磨平,从而 patch 方法只用接收新旧node即可 。
生成Dom
这里大家记住一句话即可,无论VNode 是什么类型的节点,只有三种类型的节点会被创建并插入到Dom中: 元素节点,注释节点,和文本节点 。
我们接着看下 它返回一个怎样的方法:
export function createPatchFunction(backend) {...const { modules, nodeOps } = backend// 解构出传入的集合return function (oldVnode, vnode) {// 接收新旧vnode...const isRealElement = isDef(oldVnode.nodeType) // 是否是真实Domif(isRealElement) {// $el是真实DomoldVnode = emptyNodeAt(oldVnode)// 转为VNode格式覆盖自己}...}}
首次渲染时没有,就是 $el, 一个真实的dom, 经过() 方法包装:
function emptyNodeAt(elm) {return new VNode(nodeOps.tagName(elm).toLowerCase(), // 对应tag属性{},// 对应data[],// 对应childrenundefined,//对应textelm// 真实dom赋值给了elm属性)}包装后的:{tag: 'div',elm: '' // 真实dom}-------------------------------------------------------nodeOps:export function tagName (node) {// 返回节点的标签名return node.tagName}
在将传入的==$el== 属性转为了VNode 格式之后,我们继续:
export function createPatchFunction(backend) { ...return function (oldVnode, vnode) {// 接收新旧vnodeconst insertedVnodeQueue = []...const oldElm = oldVnode.elm//包装后的真实Dom const parentElm = nodeOps.parentNode(oldElm)// 首次父节点为createElm(// 创建真实Domvnode, // 第二个参数insertedVnodeQueue,// 空数组parentElm,// nodeOps.nextSibling(oldElm)// 下一个节点)return vnode.elm // 返回真实Dom覆盖vm.$el}}------------------------------------------------------nodeOps:export function parentNode (node) {// 获取父节点return node.parentNode }export function nextSibling(node) {// 获取下一个节点return node.nextSibing}