_update
这个方法是干什么的呢,前面我们生成了vnode,下一步就是把vnode映射到真实的DOM节点上,这里包含了初次渲染和后面的更新渲染,拿首次渲染为例,整理参数之后会去调用vm.__patch__方法,接着会去判断浏览器环境或node.js上,前者转到patch函数的调用,后者返回一个空函数,因为在服务端是不存在DOM的。
让我们把目光转到patch函数上,他是createPatchFunction函数返回的结果:
export const patch: Function = createPatchFunction({ nodeOps, modules })
看一下函数签名,接受一个对象,前者是一些常见的DOM操作,后者则是对常见attributes的一些钩子函数的处理。
在createPatchFunction中定义了很多辅助函数,最后返回的还是一个patch,回顾整个过程,其实是使用了柯里化的技巧,整个createPatchFunction过程使用到的一些函数都是与平台强相关的,所以相当于把与平台强相关的内容体取出来集成到了createPatchFunction之中,在patch中可以忽视平台的差异化参数。
patch
略过一系列定义的函数,在800多行我们终于能看到patch的全貌:
return function patch(oldVnode, vnode, hydrating, removeOnly) {
...
}
由函数调用时:
vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
第一个参数是真实的DOM,第二个则是之前的虚拟DOM,后面同样是涉及服务端渲染,暂时舍弃,默认为false。这是初始化的时候的调用,如果是更新过程,则调用如下:
vm.$el = vm.__patch__(prevVnode, vnode);
接着往下走,对于第一次渲染,会走到:
oldVnode = emptyNodeAt(oldVnode);
这里其实就是把真实的DOM转化为vnode,接着便是最重要的把vnode挂载到document内部,这里利用了createElm函数。
在createElm内部,我们又会遇到一个常见的错误:
warn(
"Unknown custom element: <" +
tag +
"> - did you " +
"register the component correctly? For recursive components, " +
'make sure to provide the "name" option.',
vnode.context
);
这里即组件没有注册时会出现的错误。
接着我们会转到一个很重要的逻辑,即创建真实的DOM节点:
vnode.elm = vnode.ns
? nodeOps.createElementNS(vnode.ns, tag)
: nodeOps.createElement(tag, vnode);
setScope(vnode);
这里根据有无namespace分成两种,我们先看nodeOps.createElement,这其实就是我们用的dom的操作的简单封装,接着会走一个createChildren逻辑,即如果有子节点依次创建即可:
createChildren(vnode, children, insertedVnodeQueue);
接着会使用:
insert(parentElm, vnode.elm, refElm);
方法去在父节点添加子节点,同样对于insert方法,也只是对原生DOM的简单封装,只不过会做一些平台校验之类的额外工作。
当然对于顺序来说,还是先创建子节点,然后再去递归创建父节点,最终挂载到响应的位置。
destroy old
if (isDef(parentElm)) {
removeVnodes([oldVnode], 0, 0);
} else if (isDef(oldVnode.tag)) {
invokeDestroyHook(oldVnode);
}
挂载成功还有最后一步,举例来说,这里我们会创建一个新的div id=app,而原来旧的div id=app的空姐的仍然存在,所以最后一步需要把旧的节点销毁,这样才走完了全部的数据驱动流程。
总结
数据驱动答题流程就这样结束了,当然这里至少简单分析了初次挂载,后面会更加深入的介绍,总体来说收获还是很大的,继续加油