【Vue2 源码05】Render

132 阅读3分钟
// file:/src/platform/web/runtime/index.js
Vue.prototype.$mount = el => return mountComponent(vm, el) 

// file:/src/core/instance/lifecycle.js
updateComponent = () => {
    vm._update(vm._render())
}

编译外置

平时写vue代码使用了SPA:vue-cli => webpack => vue-loader,项目中引入vuenode_modulesvue 也指向"main": "dist/vue.runtime.common.js",只包含运行时版

// 平台入口处 `/src/platform/web/entry-runtime-with-compiler.js`复写了$mount方法
const mount = Vue.prototype.$mount
Vue.prototype.$mount = function(){
    ...
    const { render, staticRenderFns } = compileToFunctions(template...)
    ...
    return mount.call(this, el, hydrating)
}

渲染函数

vm._render()返回 VNode

initRender

// file:/src/core/instance/render.js
vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)

createElement

// file:/src/core/vdom/create-element.js
// 包装参数,传递给 _createElement
createElement = (context: Component, tag: any, data: any, children: any, n:any
  alwaysNormalize: boolean)
      => return _createElement(context, tag, data, children, n)

ns 在 svg 或 math 的命名空间内

_createElement

// 1. 拦截data、tag为空 return createEmptyVNode(),警告key为基本类型
// 2. 处理children为数组:若为单子级,则设为default插槽
// 3. 创建 *组件VNode* 或 *常规VNode*
// 4. 返回VNode

export function _createElement (
 context: Component,
 tag?: string | Class<Component> | Function | Object,
 data?: VNodeData,
 children?: any,
 normalizationType?: number
): VNode | Array<VNode> {
   // 1. 拦截data、tag为空 return createEmptyVNode(),警告key为基本类型
   if (isDef(data) && isDef((data: any).__ob__)) {
       return createEmptyVNode()
     }
   if (isDef(data) && isDef(data.is)) {
       tag = data.is
   }
   if (!tag) {
       return createEmptyVNode()
   }
   if (process.env.NODE_ENV !== 'production' &&
       isDef(data) && isDef(data.key) && !isPrimitive(data.key)) { ...warning }
 
   // 2. 处理children为数组:若为单子级,则设为default插槽
   if (Array.isArray(children) && typeof children[0] === 'function') {
       data = data || {}
       data.scopedSlots = { default: children[0] }
       children.length = 0
   }
   if (normalizationType === ALWAYS_NORMALIZE) {
       children = normalizeChildren(children)
   } else if (normalizationType === SIMPLE_NORMALIZE) {
       children = simpleNormalizeChildren(children)
   }
   
   // 3. 创建 *组件VNode* 或 *常规VNode*
   let vnode, ns
   if (typeof tag === 'string') {
       let Ctor
       ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
       if (config.isReservedTag(tag)) {
           vnode = new VNode(
               config.parsePlatformTagName(tag), data, children,
               undefined, undefined, context
           )
       } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
           vnode = createComponent(Ctor, data, context, children, tag)
       } else {
           vnode = new VNode(
               tag, data, children,
               undefined, undefined, context
           )
       }
   } else {
       vnode = createComponent(tag, data, context, children)
   }
 
   // 4. 返回VNode
   if (Array.isArray(vnode)) {
       return vnode
   } else if (isDef(vnode)) {
       if (isDef(ns)) applyNS(vnode, ns)
       if (isDef(data)) registerDeepBindings(data)
       return vnode
   } else {
       return createEmptyVNode()
   }
   
}
  • createComponent
// 1. 前处理
// 2. 装载数据:mixin 重注、v-model语法糖、propsData依赖、组件事件、注入回调
// 3. 返回

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
    // 1. 前处理
    if (isUndef(Ctor)) return
    const baseCtor = context.$options._base
    if (isObject(Ctor)) {
        Ctor = baseCtor.extend(Ctor)
    }
    if (typeof Ctor !== 'function') return
    // 服务端渲染
    let asyncFactory
    if (isUndef(Ctor.cid)) {
        asyncFactory = Ctor
        Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
        if (Ctor === undefined) {
          return createAsyncPlaceholder( ... )
        }
    }
    
    // 2. 装载数据
    data = data || {}
    resolveConstructorOptions(Ctor) // mixin 重注入,以防丢失
    if (isDef(data.model)) transformModel(Ctor.options, data) // v-model语法糖
    
    const propsData = extractPropsFromVNodeData(data, Ctor, tag) 
    // a.提取属性prop、attr到propsData
    // b. 若有响应值,则此时产生依赖,父级向子组件传递响应值
    
    if (isTrue(Ctor.options.functional)) {
        // 函数式组件
        return createFunctionalComponent(Ctor, propsData, data, context, children)
    }
    const listeners = data.on // 组件自身调用事件
    data.on = data.nativeOn // 原生事件流
    installComponentHooks(data) // 注入回调init、prepatch、insert、destroy
    
    // 3. 返回
    const name = Ctor.options.name || tag
    const vnode = new VNode(
        `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
        data, undefined, undefined, undefined, context,
        { Ctor, propsData, listeners, tag, children },
        asyncFactory
    )
    
    return vnode
}

createTextVNode

_v() 指向=> createTextVNode()

// val 常常为 *响应值*,所以常从此处依赖
export function createTextVNode (val: string | number) {
  return new VNode(undefined, undefined, undefined, String(val)) // this.text = text
}

虚拟dom

浏览器的标准把 DOM 设计的非常复杂。频繁的去更新 DOM ,会产生一定的性能问题。

  • VNode 简单对象
export default class VNode {
  constructor (
    tag?: string,
    data?: VNodeData,
    children?: ?Array<VNode>,
    text?: string,
    elm?: Node,
    context?: Component,
    componentOptions?: VNodeComponentOptions,
    asyncFactory?: Function
  ) {
    this.tag = tag
    this.data = data
    this.children = children
    this.text = text
    this.elm = elm
    this.ns = undefined
    this.context = context
    this.fnContext = undefined
    this.fnOptions = undefined
    this.fnScopeId = undefined
    this.key = data && data.key
    this.componentOptions = componentOptions
    this.componentInstance = undefined
    this.parent = undefined
    this.raw = false
    this.isStatic = false
    this.isRootInsert = true
    this.isComment = false
    this.isCloned = false
    this.isOnce = false
    this.asyncFactory = asyncFactory
    this.asyncMeta = undefined
    this.isAsyncPlaceholder = false
  }
}

Virtual DOM 是借鉴了一个开源库 snabbdom 引自Vue.js 技术揭秘

DOM操作

vm._update() => vm.__patch__() => createPatchFunction() => patch()

Vue.prototype._update

// file:/src/core/instance/lifecycle.js

// 1. 记录前后值
// 2. 根据前后记录,来排队初始化渲染还是更新渲染
// 3. $el 标记更新

export function lifecycleMixin(Vue: Class<Component>) {
    Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
        // 1. 记录前后值
        const vm: Component = this
        const prevEl = vm.$el
        const prevVnode = vm._vnode
        const restoreActiveInstance = setActiveInstance(vm) // 运行标记(单例)
        vm._vnode = vnode
        
        // 2. 对比前后,判断渲染
        if (!prevVnode) {
            vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false) // 初始化
        } else {
            vm.$el = vm.__patch__(prevVnode, vnode) // 更新
        }
        restoreActiveInstance() // 运行标记(回溯)
        
        // 3. HTML标记vue实例
        if (prevEl) {
            prevEl.__vue__ = null
        }
        if (vm.$el) {
            vm.$el.__vue__ = vm
        }
        if (vm.$vnode && vm.$parent && vm.$vnode === vm.$parent._vnode) {
            // 更新高阶组件
            vm.$parent.$el = vm.$el
        }
    }
    
    Vue.prototype.$forceUpdate = function () {}
    Vue.prototype.$destroy = function () {}
}

createPatchFunction

// file:/src/platform/web/runtime/patch.js

import baseModules from 'core/vdom/modules/index'
import platformModules from 'web/runtime/modules/index'
// 传递 平台元素操作的工具函数 与 vnode各模块生命周期回调
export const patch: Function = createPatchFunction({ nodeOps, modules })

patch

export function createPatchFunction (backend) {
    // ... 辅助函数
    return function patch (oldVnode, vnode, hydrating, removeOnly) {
        // 1. 参数判断,选择入口
        if (isUndef(vnode)) {
            // 无新vnode 销毁
            if (isDef(oldVnode)) invokeDestroyHook(oldVnode)
            return
        }
        let isInitialPatch = false
        const insertedVnodeQueue = []
        if (isUndef(oldVnode)) {
            // 无旧vnode 创建并设置 *插入回调队列*
            isInitialPatch = true
            createElm(vnode, insertedVnodeQueue)
        } else {
            const isRealElement = isDef(oldVnode.nodeType)
            if (!isRealElement && sameVnode(oldVnode, vnode)) {
                // **diff函数**
                patchVnode(oldVnode, vnode, insertedVnodeQueue, null, null, removeOnly) 
            } else {
                if (isRealElement) {
                // 根实例初始化入口
                    // 服务端渲染
                    if (oldVnode.nodeType === 1 && oldVnode.hasAttribute(SSR_ATTR)) {
                        oldVnode.removeAttribute(SSR_ATTR)
                        hydrating = true
                    }
                    if (isTrue(hydrating)) {}

                    oldVnode = emptyNodeAt(oldVnode) // $el转换为vnode
                }

                const oldElm = oldVnode.elm
                const parentElm = nodeOps.parentNode(oldElm)

                createElm(
                    vnode,
                    insertedVnodeQueue,
                    oldElm._leaveCb ? null : parentElm,
                    nodeOps.nextSibling(oldElm)
                )

                // 递归更新父级占位node
                if (isDef(vnode.parent)) {...}

                // 销毁旧vnode
                if (isDef(parentElm)) {
                    // 根实例清除$el
                    removeVnodes([oldVnode], 0, 0)
                } else if (isDef(oldVnode.tag)) {
                    // 组件调用销毁钩子
                    invokeDestroyHook(oldVnode)
                }
            }
        }
        
        invokeInsertHook(vnode, insertedVnodeQueue, isInitialPatch)
        return vnode.elm
    }
}
invokeDestroyHook
// 销毁钩子
function invokeDestroyHook (vnode) {
    let i, j
    const data = vnode.data
    if (isDef(data)) {
        if (isDef(i = data.hook) && isDef(i = i.destroy)) i(vnode) // 组件钩子
        for (i = 0; i < cbs.destroy.length; ++i) cbs.destroy[i](vnode) // 模块钩子
    }
    // 递归子级
    if (isDef(i = vnode.children)) {
        for (j = 0; j < vnode.children.length; ++j) {
            invokeDestroyHook(vnode.children[j])
        }
    }
}
createElm
// 创建元素
function createElm (
    vnode,
    insertedVnodeQueue,
    parentElm,
    refElm,
    nested,
    ownerArray,
    index
) {
    // 判断入口,创建相应node

    if (isDef(vnode.elm) && isDef(ownerArray)) {
        vnode = ownerArray[index] = cloneVNode(vnode)
    }

    vnode.isRootInsert = !nested // transition 标记
    // 组件入口
    if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
        return
    }

    const data = vnode.data
    const children = vnode.children
    const tag = vnode.tag
    if (isDef(tag)) {
    // 标签入口
        vnode.elm = vnode.ns
            ? nodeOps.createElementNS(vnode.ns, tag)
            : nodeOps.createElement(tag, vnode)
        setScope(vnode)

        createChildren(vnode, children, insertedVnodeQueue) // 递归子级
        if (isDef(data)) {
            invokeCreateHooks(vnode, insertedVnodeQueue) // 调用模块
        }
        insert(parentElm, vnode.elm, refElm) // 注入
    } else if (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)
    }
}
insert
function insert (parent, elm, ref) {
    if (isDef(parent)) {
        if (isDef(ref)) {
            if (nodeOps.parentNode(ref) === parent) {
              nodeOps.insertBefore(parent, elm, ref) // 兄弟级别插入
            }
        } else {
            nodeOps.appendChild(parent, elm) // 插入子级
        }
    }
}
createChildren
function createChildren (vnode, children, insertedVnodeQueue) {
    if (Array.isArray(children)) {
    // 递归
        for (let i = 0; i < children.length; ++i) {
            createElm(children[i], insertedVnodeQueue, vnode.elm, null, true, children, i)
        }
    } else if (isPrimitive(vnode.text)) {
        // 直接插入文本
        nodeOps.appendChild(vnode.elm, nodeOps.createTextNode(String(vnode.text)))
    }
}
createComponent
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
    let i = vnode.data
    if (isDef(i)) {
        // keepAlive 包裹
        const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
        if (isDef(i = i.hook) && isDef(i = i.init)) {
        // 调用data.hook.init 初始化组件
            i(vnode, false /* hydrating */)
        }
        // 必然设置了vnode.componentInstance
        if (isDef(vnode.componentInstance)) {
            initComponent(vnode, insertedVnodeQueue)
            insert(parentElm, vnode.elm, refElm)
            if (isTrue(isReactivated)) {
                reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
            }
            return true
        }
    }
}

nodeOps

create
export function createElement (tagName: string, vnode: VNode): Element {
  const elm = document.createElement(tagName)
  if (tagName !== 'select') {
    return elm
  }
  // false or null will remove the attribute but undefined will not
  if (vnode.data && vnode.data.attrs && vnode.data.attrs.multiple !== undefined) {
    elm.setAttribute('multiple', 'multiple')
  }
  return elm
}

export function createTextNode (text: string): Text {
  return document.createTextNode(text)
}

export function createComment (text: string): Comment {
  return document.createComment(text)
}
operate
export function insertBefore (parentNode: Node, newNode: Node, referenceNode: Node) {
  parentNode.insertBefore(newNode, referenceNode)
}

export function removeChild (node: Node, child: Node) {
  node.removeChild(child)
}

export function appendChild (node: Node, child: Node) {
  node.appendChild(child)
}

export function setTextContent (node: Node, text: string) {
  node.textContent = text
}

export function setStyleScope (node: Element, scopeId: string) {
  node.setAttribute(scopeId, '')
}
relation
export function parentNode (node: Node): ?Node {
  return node.parentNode
}

export function nextSibling (node: Node): ?Node {
  return node.nextSibling
}

export function tagName (node: Element): string {
  return node.tagName
}