You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Vue.prototype._update=function(vnode: VNode,hydrating?: boolean){constvm: Component=thisconstprevEl=vm.$elconstprevVnode=vm._vnodeconstprevActiveInstance=activeInstanceactiveInstance=vmvm._vnode=vnode// Vue.prototype.__patch__ is injected in entry points// based on the rendering backend used.if(!prevVnode){// initial rendervm.$el=vm.__patch__(vm.$el,vnode,hydrating,false/* removeOnly */)}else{// updatesvm.$el=vm.__patch__(prevVnode,vnode)}activeInstance=prevActiveInstance// update __vue__ referenceif(prevEl){prevEl.__vue__=null}if(vm.$el){vm.$el.__vue__=vm}// if parent is an HOC, update its $el as wellif(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.}
最开始的几个变量是在数据更新的时候用的,所以这里可以不管,把它们当做为空,接着进入了 initial render 中。然后它调用了一个 vm.__patch__ 方法,这个方法实际上在不同的平台,比如 web 和 weex 上的定义是不一样的,因此在 web 平台中它的定义在 src/platforms/web/runtime/index.js 中:
import*asnodeOpsfrom'web/runtime/node-ops'import{createPatchFunction}from'core/vdom/patch'importbaseModulesfrom'core/vdom/modules/index'importplatformModulesfrom'web/runtime/modules/index'// the directive module should be applied last, after all// built-in modules have been applied.constmodules=platformModules.concat(baseModules)exportconstpatch: Function=createPatchFunction({ nodeOps, modules })
consthooks=['create','activate','update','remove','destroy']exportfunctioncreatePatchFunction(backend){leti,jconstcbs={}const{ modules, nodeOps }=backendfor(i=0;i<hooks.length;++i){cbs[hooks[i]]=[]for(j=0;j<modules.length;++j){if(isDef(modules[j][hooks[i]])){cbs[hooks[i]].push(modules[j][hooks[i]])}}}// ...returnfunctionpatch(oldVnode,vnode,hydrating,removeOnly){if(isUndef(vnode)){if(isDef(oldVnode))invokeDestroyHook(oldVnode)return}letisInitialPatch=falseconstinsertedVnodeQueue=[]if(isUndef(oldVnode)){// empty mount (likely as component), create new root elementisInitialPatch=truecreateElm(vnode,insertedVnodeQueue)}else{constisRealElement=isDef(oldVnode.nodeType)if(!isRealElement&&sameVnode(oldVnode,vnode)){// patch existing root nodepatchVnode(oldVnode,vnode,insertedVnodeQueue,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)returnoldVnode}elseif(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 itoldVnode=emptyNodeAt(oldVnode)}// replacing existing elementconstoldElm=oldVnode.elmconstparentElm=nodeOps.parentNode(oldElm)// create new nodecreateElm(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, recursivelyif(isDef(vnode.parent)){letancestor=vnode.parentconstpatchable=isPatchable(vnode)while(ancestor){for(leti=0;i<cbs.destroy.length;++i){cbs.destroy[i](ancestor)}ancestor.elm=vnode.elmif(patchable){for(leti=0;i<cbs.create.length;++i){cbs.create[i](emptyNode,ancestor)}// #6513// invoke insert hooks that may have been merged by create hooks.// e.g. for directives that uses the "inserted" hook.constinsert=ancestor.data.hook.insertif(insert.merged){// start at index 1 to avoid re-invoking component mounted hookfor(leti=1;i<insert.fns.length;i++){insert.fns[i]()}}}else{registerRef(ancestor)}ancestor=ancestor.parent}}// destroy old nodeif(isDef(parentElm)){removeVnodes(parentElm,[oldVnode],0,0)}elseif(isDef(oldVnode.tag)){invokeDestroyHook(oldVnode)}}}invokeInsertHook(vnode,insertedVnodeQueue,isInitialPatch)returnvnode.elm}}
constisRealElement=isDef(oldVnode.nodeType)if(!isRealElement&&sameVnode(oldVnode,vnode)){// patch existing root nodepatchVnode(oldVnode,vnode,insertedVnodeQueue,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)returnoldVnode}elseif(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 itoldVnode=emptyNodeAt(oldVnode)}// replacing existing elementconstoldElm=oldVnode.elmconstparentElm=nodeOps.parentNode(oldElm)// create new nodecreateElm(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))}
由于传入的 oldVnode 实际上是一个 DOM container,所以 isRealElement 为 true,接下来又通过 emptyNodeAt 方法把 oldVnode 转换成 VNode 对象,然后再调用 createElm 方法,createElm 的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点上,这个方法在这里非常重要:
functioncreateElm(vnode,insertedVnodeQueue,parentElm,refElm,nested,ownerArray,index){if(isDef(vnode.elm)&&isDef(ownerArray)){// This vnode was used in a previous render!// now it's used as a new node, overwriting its elm would cause// potential patch errors down the road when it's used as an insertion// reference node. Instead, we clone the node on-demand before creating// associated DOM element for it.vnode=ownerArray[index]=cloneVNode(vnode)}vnode.isRootInsert=!nested// for transition enter checkif(createComponent(vnode,insertedVnodeQueue,parentElm,refElm)){return}constdata=vnode.dataconstchildren=vnode.childrenconsttag=vnode.tagif(isDef(tag)){if(process.env.NODE_ENV!=='production'){if(data&&data.pre){creatingElmInVPre++}if(isUnknownElement(vnode,creatingElmInVPre)){warn('Unknown custom element: <'+tag+'> - did you '+'register the component correctly? For recursive components, '+'make sure to provide the "name" option.',vnode.context)}}vnode.elm=vnode.ns
? nodeOps.createElementNS(vnode.ns,tag)
: nodeOps.createElement(tag,vnode)setScope(vnode)/* istanbul ignore if */if(__WEEX__){// ...}else{createChildren(vnode,children,insertedVnodeQueue)if(isDef(data)){invokeCreateHooks(vnode,insertedVnodeQueue)}insert(parentElm,vnode.elm,refElm)}if(process.env.NODE_ENV!=='production'&&data&&data.pre){creatingElmInVPre--}}elseif(isTrue(vnode.isComment)){vnode.elm=nodeOps.createComment(vnode.text)insert(parentElm,vnode.elm,refElm)}else{vnode.elm=nodeOps.createTextNode(vnode.text)insert(parentElm,vnode.elm,refElm)}}
将 data,children,tag 拿出来,对 tag 做一个检测,如果标签没有声明就使用(比如组件),就会报错。接着创建元素:
_update
方法是实例的一个私有方法,它被调用的时机有2个:一个是首次渲染,一个是数据更新的时候,本篇先只说一下首次渲染,它的作用是将VNode
转换成真实的DOM,代码在:src/core/instance/lifecycle.js
:最开始的几个变量是在数据更新的时候用的,所以这里可以不管,把它们当做为空,接着进入了 initial render 中。然后它调用了一个
vm.__patch__
方法,这个方法实际上在不同的平台,比如 web 和 weex 上的定义是不一样的,因此在 web 平台中它的定义在src/platforms/web/runtime/index.js
中:如果是服务器渲染,就没有真实的DOM环境,所以就不需要转换,因此是一个空函数,而在浏览器中,指向了
patch
方法,它的定义在src/platforms/web/runtime/patch.js
中:patch
是调用了createPatchFunction
,这里传入了一个对象,包含nodeOps
参数和modules
参数。其中,nodeOps
封装了一系列 DOM 操作的方法,modules
定义了一些模块的钩子函数的实现(比如attr,class,style,event等),这里先忽略,来看一下createPatchFunction
的实现,它定义在src/core/vdom/patch.js
中:最开始的循环,是将上面定义的
hook
钩子函数初始化,重要的是返回patch
函数,这里定义一个例子:上篇说道在
vm._update
的方法里是这么调用patch
方法的:那第一个参数就是 id 为 app 的dom对象,
vm.$el
的赋值是在之前mountComponent
函数做的,vnode
对应的是调用render
函数的返回值,hydrating
在非服务端渲染情况下为 false,removeOnly
为 false。接着跳到关键步骤:
由于传入的
oldVnode
实际上是一个 DOM container,所以isRealElement
为 true,接下来又通过emptyNodeAt
方法把oldVnode
转换成VNode
对象,然后再调用createElm
方法,createElm
的作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点上,这个方法在这里非常重要:将
data
,children
,tag
拿出来,对tag
做一个检测,如果标签没有声明就使用(比如组件),就会报错。接着创建元素:这里调用的是:
nodeOps.createElement(tag, vnode)
,也就是调用了原生API:document.createElement
,代码在:src/platforms/web/runtime/node-ops.js
,到这里就创建了一个dom,接着判断如果这个vnode还有子节点,就先创建子节点,也就是:如果
children
是一个数组,就遍历它进行递归创建,把当前的vnode.elm
作为父节点插入,否则就执行appendChild
。接着再调用
invokeCreateHooks
方法执行所有的 create 的钩子并把vnode
push 到insertedVnodeQueue
中:接着再调用
insert
方法去插入,递归调用会优先判断子元素的insert
,所以整个vnode
节点的插入顺序是先子后父:一个父节点,一个当前节点,一个参考节点,参考节点是父节点就调用
insertBefore
,否则就appendChild
,这两个也是对原生 dom 的封装。所以真实的插入dom,其实就是这个insert
。在
createElm
过程中,如果vnode
节点不包含tag
,则它有可能是一个注释或者纯文本节点,可以直接插入到父元素中:捋一遍:
首先定义了一个当前元素
elm
,然后调用createChildren
去插入子元素,所以整个插入顺序是先插入子,子拿到节点之后再插入父,代码中是先createChildren
,然后再insert
:而
createChildren
又会递归调用createElm
,最终挂载到真实的dom上(parentElm),也就是body上,到此vnode
创建为真实的 dom 结束。至此,数据初次渲染就全部完成了:
The text was updated successfully, but these errors were encountered: