在浏览器端渲染中使用的是patch方法,
它的定义在 src/platforms/web/runtime/patch.js中:
import*as nodeOps from'web/runtime/node-ops'import{ createPatchFunction }from'core/vdom/patch'import baseModules from'core/vdom/modules/index'import platformModules from'web/runtime/modules/index'// the directive module should be applied last, after all// built-in modules have been applied.const modules = platformModules.concat(baseModules)// nodeOps 封装了一系列 DOM 操作的方法,// modules 定义了一些模块的钩子函数的实现exportconstpatch: Function =createPatchFunction({ nodeOps, modules })
//createPatchFunction 定义在 src/core/vdom/patch.js
这个方法很长我们用伪代码来说一下大体内容
createPatchFunction(不变的配置参数){
拿到配置的参数
定义若干的节点处理方法
返回生成真实dom的patch函数
}
初次渲染patch处理
...// 初次渲染oldVnode为挂载的节点是真实的dom// 所以isRealElement为trueconst isRealElement =isDef(oldVnode.nodeType)if(!isRealElement &&sameVnode(oldVnode, vnode)){// patch existing root nodepatchVnode(oldVnode, vnode, insertedVnodeQueue,null,null, removeOnly)}else{if(isRealElement){...服务端渲染相关
// either not server-rendered, or hydration failed.// create an empty node and replace it// 通过 emptyNodeAt 方法把 oldVnode 转换成 VNode 对象// 只会处理第一层
oldVnode =emptyNodeAt(oldVnode)}// replacing existing element// oldElm 即为我们的挂载节点const oldElm = oldVnode.elm
// 一般首次渲染parentElm元素即为body,const parentElm = nodeOps.parentNode(oldElm)// 实际上整个过程就是递归创建了一个完整的 DOM 树并插入到 Body 上。// 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)// oldElm的兄弟节点)// createElm执行完毕body插入了一个新的dom// 触发浏览器更新,渲染出新结构...}
// 作用是通过虚拟节点创建真实的 DOM 并插入到它的父节点中functioncreateElm(vnode,
insertedVnodeQueue,
parentElm,
refElm,
nested,
ownerArray,
index){
console.log('vvv_createElm', vnode, parentElm);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}const data = vnode.data
const children = vnode.children
const tag = vnode.tag
//校验tag, 像div这种if(isDef(tag)){if(process.env.NODE_ENV!=='production'){if(data && data.pre){
creatingElmInVPre++}if(isUnknownElement(vnode, creatingElmInVPre)){...警告提示
}}// 根据平台生成真实dom
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)){// 调用create相关钩子invokeCreateHooks(vnode, insertedVnodeQueue)}// insert 方法把 DOM 插入到父节点中,因为是递归调用,// 子元素会优先调用 insert,// 所以整个 vnode 树节点的插入顺序是先子后父// insert 逻辑很简单,调用一些 nodeOps 把子节点插入到父节点中insert(parentElm, vnode.elm, refElm)}if(process.env.NODE_ENV!=='production'&& data && data.pre){
creatingElmInVPre--}// 如果 vnode 节点不包含 tag,// 则它有可能是一个注释或者纯文本节点,可以直接插入到父元素中。}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)}}
//遍历子虚拟节点,递归调用 createElm, vnode.elm 为当前子元素的父节点functioncreateChildren(vnode, children, insertedVnodeQueue){if(Array.isArray(children)){if(process.env.NODE_ENV!=='production'){checkDuplicateKeys(children)}for(let i =0; i < children.length;++i){createElm(children[i], insertedVnodeQueue, vnode.elm,null,true, children, i)}}elseif(isPrimitive(vnode.text)){
nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))}}// 执行所有的 create 相关的钩子并把 vnode push 到 insertedVnodeQueue 中。functioninvokeCreateHooks(vnode, insertedVnodeQueue){for(let i =0; i < cbs.create.length;++i){// 执行create钩子
cbs.create[i](emptyNode, vnode)}
i = vnode.data.hook // Reuse variableif(isDef(i)){if(isDef(i.create)) i.create(emptyNode, vnode)if(isDef(i.insert)) insertedVnodeQueue.push(vnode)}}
cbs.create涉及的方法
functioninsert(parent, elm, ref){if(isDef(parent)){if(isDef(ref)){if(ref.parentNode === parent){
nodeOps.insertBefore(parent, elm, ref)}}else{
nodeOps.appendChild(parent, elm)}}}// src/platforms/web/runtime/node-ops.j// 调用原生 DOM 的 API 进行 DOM 操作exportfunctioninsertBefore(parentNode: Node,newNode: Node,referenceNode: Node){
parentNode.insertBefore(newNode, referenceNode)}exportfunctionappendChild(node: Node,child: Node){
node.appendChild(child)}
把模板和数据如何渲染成最终的 DOM 的过程分析到此