内容简介:上回我们了解了上次我们提到的简单梳理下这段代码的逻辑:
上回我们了解了 vnode
从创建到生成的流程,这回我们来探索 Vue
是如何将 vnode
转化成真实的 dom
节点/元素
Vue.prototype._update
上次我们提到的 _render
函数其实作为 _update
函数的参数传入,换句话说, _render
函数结束后 _update
将会执行:point_down:
Vue.prototype._update = function (vnode, hydrating) { var vm = this; var prevEl = vm.$el; var prevVnode = vm._vnode; var restoreActiveInstance = setActiveInstance(vm); vm._vnode = vnode; // Vue.prototype.__patch__ is injected in entry points // based on the rendering backend used. if (!prevVnode) { // initial render vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */); } else { // updates vm.$el = vm.__patch__(prevVnode, vnode); } restoreActiveInstance(); // update __vue__ reference if (prevEl) { prevEl.__vue__ = null; } if (vm.$el) { vm.$el.__vue__ = vm; } // if parent is an HOC, update its $el as well if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) { vm.$parent.$el = vm.$el; } // updated hook is called by the scheduler to ensure that children are // updated in a parent's updated hook. }; 复制代码
简单梳理下这段代码的逻辑:
- 调用
setActiveInstance(vm)
设置当前的vm
为活跃的实例 - 判断
preVnode
是否存在,是则调用vm.$el = vm.__patch__(prevVnode, vnode);
,否则调用vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */);
(其实也就是第一次渲染跟二次更新的区别) - 调用
restoreActiveInstance()
重置活跃的实例 - 对
HOC
做了特殊判断(因为没用过HOC
,所以这里直接略过)
从上面整理下来的逻辑中,我们能得到讯息仅仅只有 setActiveInstance
函数返回一个闭包函数(当然这并不是很重要),如果需要更深入的了解,还需要了解 __patch__
函数是怎么实现的
其他相关代码:
updateComponent = function () { vm._update(vm._render(), hydrating); }; ... new Watcher(vm, updateComponent, noop, { before: function before () { if (vm._isMounted && !vm._isDestroyed) { callHook(vm, 'beforeUpdate'); } } }, true /* isRenderWatcher */); 复制代码
__patch__
说出来你可能不信, __patch__
函数的实现其实很简单:point_down:
var patch = createPatchFunction({ nodeOps: nodeOps, modules: modules }); ... Vue.prototype.__patch__ = inBrowser ? patch : noop; 复制代码
很明显, createPatchFunction
也是返回了一个闭包函数
patch
虽然 __patch__
外表看起来很简单,但是其实内部实现的逻辑还是挺复杂的,代码量也非常多:point_down:
return function patch (oldVnode, vnode, hydrating, removeOnly) { if (isUndef(vnode)) { if (isDef(oldVnode)) { invokeDestroyHook(oldVnode); } return } var isInitialPatch = false; var insertedVnodeQueue = []; if (isUndef(oldVnode)) { // empty mount (likely as component), create new root element isInitialPatch = true; createElm(vnode, insertedVnodeQueue); } else { var isRealElement = isDef(oldVnode.nodeType); if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly); } else { if (isRealElement) { // mounting to a real element // check if this is server-rendered content and if we can perform // a successful hydration. if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) { oldVnode.removeAttribute(SSR_ATTR); hydrating = true; } if (isTrue(hydrating)) { if (hydrate(oldVnode, vnode, insertedVnodeQueue)) { invokeInsertHook(vnode, insertedVnodeQueue, true); return oldVnode } else if (process.env.NODE_ENV !== 'production') { warn( 'The client-side rendered virtual DOM tree is not matching ' + 'server-rendered content. This is likely caused by incorrect ' + 'HTML markup, for example nesting block-level elements inside ' + '<p>, or missing <tbody>. Bailing hydration and performing ' + 'full client-side render.' ); } } // either not server-rendered, or hydration failed. // create an empty node and replace it oldVnode = emptyNodeAt(oldVnode); } // replacing existing element var oldElm = oldVnode.elm; var parentElm = nodeOps.parentNode(oldElm); // create new node createElm( vnode, insertedVnodeQueue, // extremely rare edge case: do not insert if old element is in a // leaving transition. Only happens when combining transition + // keep-alive + HOCs. (#4590) oldElm._leaveCb ? null : parentElm, nodeOps.nextSibling(oldElm) ); // update parent placeholder node element, recursively if (isDef(vnode.parent)) { var ancestor = vnode.parent; var patchable = isPatchable(vnode); while (ancestor) { for (var i = 0; i < cbs.destroy.length; ++i) { cbs.destroy[i](ancestor); } ancestor.elm = vnode.elm; if (patchable) { for (var i$1 = 0; i$1 < cbs.create.length; ++i$1) { cbs.create[i$1](emptyNode, ancestor); } // #6513 // invoke insert hooks that may have been merged by create hooks. // e.g. for directives that uses the "inserted" hook. var insert = ancestor.data.hook.insert; if (insert.merged) { // start at index 1 to avoid re-invoking component mounted hook for (var i$2 = 1; i$2 < insert.fns.length; i$2++) { insert.fns[i$2](); } } } else { registerRef(ancestor); } ancestor = ancestor.parent; } } // destroy old node if (isDef(parentElm)) { removeVnodes(parentElm, [oldVnode], 0, 0); } else if (isDef(oldVnode.tag)) { invokeDestroyHook(oldVnode); } } } invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch); return vnode.elm } 复制代码
这么多的代码,一下子肯定是消化不完的,所以我们可以尝试性的带着以下这几个问题来看:point_down:
- 第一次的
patch
操作与后续的patch
操作有何区别? -
dom
节点之间产生变更,或者说是「新节点」替换「老节点」时,规则是怎么样的?
patch
函数的特殊逻辑
针对初次渲染, patch
函数是做了特殊逻辑的。显然我们只要把初次执行的 patch
的逻辑走一遍就清楚了:point_down:
结合上面的源码,归纳下这里的思路:
- 若「老节点」为空,则调用
createElm(vnode, insertVnodeQueue)
来 直接创建「新节点」 - 若「老节点」为真实存在的
dom
节点,则分成以下几步:- 移除 「老节点」的
SSR_ATTR
属性(若存在) - 判断是否正在「渲染」(
hydrating
)- 是则执行
hydrate(oldvnode, vnode, insertVnodeQueue)
并判断是否执行成功invokeInsertHook(vnode, insertVnodeQueue, true)
- 否则调用
emptyNodeAt(oldVnode)
,给「老节点」(实际上是dom
节点)生成它的 "vnode
"
- 是则执行
- 移除 「老节点」的
被「遗忘」的一行代码
看完源码的同学不难不发现,上面梳理的逻辑里少了这段代码:
if (!isRealElement && sameVnode(oldVnode, vnode)) { // patch existing root node patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly); } 复制代码
也就是对「非 dom
元素的相同节点」做一次 patchVnode
的操作。关于这段代码可以分成几点来分析:
patchVnode
「相同的节点」
根据语义我们应该看这部分代码:point_down:
function sameVnode (a, b) { return ( a.key === b.key && ( ( a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b) ) || ( isTrue(a.isAsyncPlaceholder) && a.asyncFactory === b.asyncFactory && isUndef(b.asyncFactory.error) ) ) ) } 复制代码
sameVnode
的逻辑就是:按照 vnode
的属性来判断两个 「 vnode
」节点是否是同一个节点
patchVnode
由于执行 patchVnode
的前提就是新老节点是「相同」的节点,我们有理由相信,它是用来处理同个节点的变化。
function patchVnode ( oldVnode, vnode, insertedVnodeQueue, ownerArray, index, removeOnly ) { if (oldVnode === vnode) { return } if (isDef(vnode.elm) && isDef(ownerArray)) { // clone reused vnode vnode = ownerArray[index] = cloneVNode(vnode); } var elm = vnode.elm = oldVnode.elm; if (isTrue(oldVnode.isAsyncPlaceholder)) { if (isDef(vnode.asyncFactory.resolved)) { hydrate(oldVnode.elm, vnode, insertedVnodeQueue); } else { vnode.isAsyncPlaceholder = true; } return } // reuse element for static trees. // note we only do this if the vnode is cloned - // if the new node is not cloned it means the render functions have been // reset by the hot-reload-api and we need to do a proper re-render. if (isTrue(vnode.isStatic) && isTrue(oldVnode.isStatic) && vnode.key === oldVnode.key && (isTrue(vnode.isCloned) || isTrue(vnode.isOnce)) ) { vnode.componentInstance = oldVnode.componentInstance; return } var i; var data = vnode.data; if (isDef(data) && isDef(i = data.hook) && isDef(i = i.prepatch)) { i(oldVnode, vnode); } var oldCh = oldVnode.children; var ch = vnode.children; if (isDef(data) && isPatchable(vnode)) { for (i = 0; i < cbs.update.length; ++i) { cbs.update[i](oldVnode, vnode); } if (isDef(i = data.hook) && isDef(i = i.update)) { i(oldVnode, vnode); } } if (isUndef(vnode.text)) { if (isDef(oldCh) && isDef(ch)) { if (oldCh !== ch) { updateChildren(elm, oldCh, ch, insertedVnodeQueue, removeOnly); } } else if (isDef(ch)) { if (process.env.NODE_ENV !== 'production') { checkDuplicateKeys(ch); } if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } addVnodes(elm, null, ch, 0, ch.length - 1, insertedVnodeQueue); } else if (isDef(oldCh)) { removeVnodes(elm, oldCh, 0, oldCh.length - 1); } else if (isDef(oldVnode.text)) { nodeOps.setTextContent(elm, ''); } } else if (oldVnode.text !== vnode.text) { nodeOps.setTextContent(elm, vnode.text); } if (isDef(data)) { if (isDef(i = data.hook) && isDef(i = i.postpatch)) { i(oldVnode, vnode); } } } 复制代码
我们看看这段代码都做了哪些事情:
- 复用
vnode
(如果存在elem
属性) - 处理异步组件
- 处理静态节点
- 执行
prepatch
(如果存在data
属性) - 执行
update
(如果存在data
属性) - 比较
oldVnode
和vnode
两个节点 - 执行
postpatch
(如果存在data
属性)
当然,这里最直观的就是比较 oldVnode
和 vnode
两个节点的逻辑:point_down:
其他的逻辑可以留到下一篇文章再分析~
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- Golang源码探索(一) 编译和调试源码
- 使用gdb调试工具上手调试php和swoole源码
- JVM源码分析-JVM源码编译与调试
- 调试 Flink 源码
- Node.js 源码调试
- 如何断点调试Tomcat源码
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Ruby on Rails电子商务实战
Christian Hellsten、Jarkko Laine / 曹维远 / 人民邮电出版社 / 2008-4 / 49.00元
《Ruby on Rails电子商务实战》全面讲解了使用Ruby on Rails创建产品级应用程序的过程。书中通过演示构建网上书店的全过程,先后介绍如何使用如TDD的敏捷实践,启动一个项目并建立良好稳定的基础,如何深入Ruby on Rails,实现诸如将应用程序翻译成各种语言对产品进行调试等的普遍需求。其中用到的主要技术包括Ajax、聚合、设置标签和国际化等,还介绍了如何使用ActiveRec......一起来看看 《Ruby on Rails电子商务实战》 这本书的介绍吧!