上篇文章了解了patch的入口,这篇文章的目的是了解patch函数功能的一部分:创建 DOM。patch的功能大致分为 3 块:
- 有
oldVnode无vnode,调用oldVnode的destroy流程 - 无
oldVnode有vnode,调用创建dom流程 - 有
oldVnode有vnode,调用diff流程
这块的代码是:
1 | function patch(oldVnode, vnode, hydrating, removeOnly) { |
patch就会开始真正操作dom元素了,此前都是操作vnode。今天我们的重点就是最后的createElm创建元素流程. 有个细节,可以看到上面有 2 个分支会调用createElm,第一个是isUndef(oldVnode)成立的那个if,这个很好理解。 那最下面的那个createElm何时成立呢?
在Vue.prototype._update中是这样调用patch的:
1 | if (!prevVnode) { |
也就是说在初始化时传入的oldVnode实际上是vm.$el这个真实的dom元素,这时我们的isRealElement就会是true。 接下来就会描述具体创建流程。
创建 dom
此分支的具体代码:
1 | if (isRealElement) { |
可以看到主要分为 3 块: createElm、更新父元素、删除oldVnode。
createElm
1 | function createElm(vnode, insertedVnodeQueue, parentElm, refElm, nested, ownerArray, index) { |
其中createComponent用于创建自定义组件这里先略过后续再说。那么剩下的其他代码就很好理解了,大致就是利用vnode的各种属性来创建真实的 dom,最后插入到父 dom 节点上,等到下次页面渲染我们就能看到更新了。
参数上的refElm是新创建元素并插入父节点时的参照物锚点元素,在insert函数中:
1 | function insert(parent, elm, ref) { |
如果有ref则放到ref前面,如果没有则放到最后。
接下来我们主要看看createChildren和invokeCreateHooks怎么实现的。
createChildren
1 | // 为每个child生成child.elm,并以vnode.elm作为父节点 |
如果children是数组,那么递归调用元素来createElm;如果是个普通文本,那么直接创建一个文本节点插入到父节点。这样我们就生成了一个 dom 子树。
invokeCreateHooks
主要调用各种子module的create钩子,虽然代码比较短,但是涉及的内容很多。
1 | function invokeCreateHooks(vnode, insertedVnodeQueue) { |
我在注释里只说了每种module的create钩子干了什么,而没有说具体是怎么做的,这个留给大家自己去看,基本上代码都很不难。
cbs我们在上篇文章也有说过,这里再提下,它相当于一个钩子的分类汇总对象:
1 | const cbs = {}; |
backend是调用createPatchFunction传入的参数,可以在src/platforms/web/runtime/patch.js找到。
removeVnodes、invokeDestroyHook
用于删除一个vnode。
removeVnodes
1 | function removeVnodes(parentElm, vnodes, startIdx, endIdx) { |
主要就是调用remove和destroy两个钩子。
invokeDestroyHook
1 | function invokeDestroyHook(vnode) { |
可以看到和invokeCreateHooks逻辑非常类似,这里就交给大家自己看了。
最后光看代码在一些细节上很容易蒙圈,最好拿着简单的demo逐步打断点调试,例如:
1 | <div id="app"> |
可以覆盖这里说的大部分的主要场景。