上篇文章了解了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"> |
可以覆盖这里说的大部分的主要场景。