Vue源码解读(五):render和VNode

  • Post author:
  • Post category:vue


Vue 2.0 相比 Vue 1.0 最大的升级就是利用了虚拟DOM。 在 Vue 1.0 中视图的更新是纯响应式的。在进行响应式初始化的时候,一个响应式数据

key

会创建一个对应的

dep

,这个

key

在模板中被引用几次就会创建几个

watcher

。也就是一个

key

对应一个

dep



dep

内管理一个或者多个

watcher

。由于

watcher



DOM

是一对一的关系,更新时,可以明确的对某个

DOM

进行更新,更新效率还是很高的。

随着应用越来越大,组件越来越多,一个组件就可能存在大量的

watcher

,性能就成了问题。Vue 2.0 加入了虚拟DOM和 diff 后,一个组件就只需要创建一个

watcher

了,更新方式使用响应式+diff,组件之间使用响应式,组件内部使用 diff 。当数据发生变化时,通知

watcher

更新,也就是通知整个组件更新,具体更新什么元素什么位置,就可以通过 diff 算法对比新旧虚拟DOM获取差异,把差异真正的更新到视图。

虚拟DOM在 React、Vue 都有运用,一方面可以提升一些性能,另一方面也可以更好的跨平台,比如服务端渲染。

虚拟DOM,也就是VNode,本质上是使用 js 对象来模拟真实DOM中存在的节点。举个栗子:

<div id="app">
  <h1>虚拟DOM<h1>
</div>
{
  tag: "div",
  attrs: {
    id: "app"
  },
  children: [
    {
      tag: "h1",
      text: "虚拟DOM"
    }
  ]
}



VNode

Vue 中的虚拟DOM是用 一个 Class 去描述的。我们先来看看:

// src/core/vdom/vnode.js
export default class VNode {
  tag: string | void;
  data: VNodeData | void;
  children: ?Array<VNode>;
  text: string | void;
  elm: Node | void;
  ns: string | void;
  context: Component | void; // rendered in this component's scope
  key: string | number | void;
  componentOptions: VNodeComponentOptions | void;
  componentInstance: Component | void; // component instance
  parent: VNode | void; // component placeholder node
  // strictly internal
  raw: boolean; // contains raw HTML? (server only)
  isStatic: boolean; // hoisted static node
  isRootInsert: boolean; // necessary for enter transition check
  isComment: boolean; // empty comment placeholder?
  isCloned: boolean; // is a cloned node?
  isOnce: boolean; // is a v-once node?
  asyncFactory: Function | void; // async component factory function
  asyncMeta: Object | void;
  isAsyncPlaceholder: boolean;
  ssrContext: Object | void;
  fnContext: Component | void; // real context vm for functional nodes
  fnOptions: ?ComponentOptions; // for SSR caching
  devtoolsMeta: ?Object; // used to store functional render context for devtools
  fnScopeId: ?string; // functional scope id support
  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
  }
  // DEPRECATED: alias for componentInstance for backwards compat.
  /* istanbul ignore next */
  get child (): Component | void {
    return this.componentInstance
  }
}



_render

在之前介绍过,响应式数据发生变化时,触发更新其实就是执行下面代码:

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

先执行

vm._render()

,获取VNode ,做为参数传递给

vm._update

去更新视图。我们先看看

vm._render()

的定义:

// src/core/instance/render.js
export function renderMixin (Vue: Class<Component>) {
  // install runtime convenience helpers
  // 在组件实例上挂载一些运行时需要用到的工具方法
  installRenderHelpers(Vue.prototype)
  Vue.prototype.$nextTick = function (fn: Function) {
    return nextTick(fn, this)
  }
  /**
   * 通过执行 render 函数生成 VNode
   * 不过里面加了大量的异常处理代码
   */
  Vue.prototype._render = function (): VNode {
    const vm: Component = this
    const { render, _parentVnode } = vm.$options
    if (_parentVnode) {
      vm.$scopedSlots = normalizeScopedSlots(
        _parentVnode.data.scopedSlots,
        vm.$slots,
        vm.$scopedSlots
      )
    }
    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    // 设置父 vnode。这使得渲染函数可以访问占位符节点上的数据。
    vm.$vnode = _parentVnode
    // render self
    let vnode
    try {
      // There's no need to maintain a stack because all render fns are called
      // separately from one another. Nested component's render fns are called
      // when parent component is patched.
      currentRenderingInstance = vm
      // 执行 render 函数,生成 vnode
      vnode = render.call(vm._renderProxy, vm.$createElement)
    } catch (e) {
      handleError(e, vm, `render`)
      // 错误处理,开发环境渲染错误信息,生产环境返回之前的 vnode,以防止渲染错误导致组件空白
      if (process.env.NODE_ENV !== 'production' && vm.$options.renderError) {
        try {
          vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e)
        } catch (e) {
          handleError(e, vm, `renderError`)
          vnode = vm._vnode
        }
      } else {
        vnode = vm._vnode
      }
    } finally {
      currentRenderingInstance = null
    }
    // 如果 vnode 是数组且只有一项,直接放回该项
    if (Array.isArray(vnode) && vnode.length === 1) {
      vnode = vnode[0]
    }
    // render 函数出错时,返回一个空的 vnode
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn(
          'Multiple root nodes returned from render function. Render function ' +
          'should return a single root node.',
          vm
        )
      }
      vnode = createEmptyVNode()
    }
    // set parent
    vnode.parent = _parentVnode
    return vnode
  }
}


_render

中会调用

render

方法,这里有两种情况:

  • 模板编译而来的 render
with(this){return _c(tag, data, children, normalizationType)}
  • 用户定义的 render
render: function (createElement) {
  return createElement('div', {
     attrs: {
        id: 'app'
      },
  }, this.msg)
}

上面可以看出模板编译出来的

render

实际就是调用

_c

方法,用户自定义的

render

实际就是

vm.$createElement

方法。

// src/core/instance/render.js
export function initRender (vm: Component) {
/**
 * @param {*} a 标签名
 * @param {*} b 属性的 JSON 字符串
 * @param {*} c 子节点数组
 * @param {*} d 节点的规范化类型
 * @returns VNode or Array<VNode>
 */
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false)
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true)
}

这两个方法支持相同的参数,实际调用的都是

createElement

方法。



createElement

// src/core/vdom/create-element.js
// 生成组件或普通标签的 vnode,一个包装器函数,用于提供更灵活的接口。
export function createElement (
  context: Component,
  tag: any,
  data: any,
  children: any,
  normalizationType: any,
  alwaysNormalize: boolean
): VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children
    children = data
    data = undefined
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE
  }
  // 执行 _createElement 方法创建组件的 VNode
  return _createElement(context, tag, data, children, normalizationType)
}



_createElement

// src/core/vdom/create-element.js
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  if (isDef(data) && isDef((data: any).__ob__)) {
    // 如果 data 是一个响应式对象,返回空节点的Vnode
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // 动态组件:is 属性被设置为 false 时
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive key
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    if (!__WEEX__ || !('@binding' in data.key)) {
      warn(
        'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
        context
      )
    }
  }
  // support single function children as default scoped slot
  // 子节点数组只有一个函数,把它默认插槽,然后清空自己子节点数组
  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)
  }
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // 平台内置的节点
      // platform built-in elements
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
        warn(
          `The .native modifier for v-on is only valid on components but it was used on <${tag}>.`,
          context
        )
      }
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // tag 是自定义组件
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      // 未知的标签 tag ,在运行时检查,因为当其父项规范化子项时,可能会为其分配命名空间
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // tag 非字符串时
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  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()
  }
}


_createElement

方法内一开先是对一些特殊情况的判断,我们只关注主流程,也就是生成 VNode 这一块,这里对

tag

先是判断如果是

string

类型,有三种情况:

  • 如果是一些平台内置节点,直接创建一个普通的 VNode.
  • 如果为已注册的组件名,通过

    createComponent

    创建一个组件类型的 VNode.
  • 否则创建一个未知标签名的 VNode.

这里还有一个值得注意的地方就是通过

normalizeChildren



simpleNormalizeChildren

方法对子节点进行规范化,使其每个节点都是 VNode 类型。


simpleNormalizeChildren

方法调用场景是

render

函数是编译生成的。理论上编译生成的

children

都已经是 VNode 类型的,但这里有一个例外,就是

functional component

函数式组件返回的是一个数组而不是一个根节点,所以会通过

Array.prototype.concat

方法把整个

children

数组打平,让它的深度只有一层。


normalizeChildren

方法的调用场景有 2 种,一个场景是

render

函数是用户手写的,当

children

只有一个节点的时候,Vue.js 从接口层面允许用户把

children

写成基础类型用来创建单个简单的文本节点,这种情况会调用

createTextVNode

创建一个文本节点的 VNode;另一个场景是当编译 slot、v-for 的时候会产生嵌套数组的情况,会调用

normalizeArrayChildren

方法



组件类型



createComponent

// src/core/vdom/create-component.js
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  // 组件的构造函数不存在
  if (isUndef(Ctor)) {
    return
  }
  // context.$options._base = Vue.options._base = Vue
  const baseCtor = context.$options._base
  // Vue.extend 定义在 src/core/global-api/extend.js
  // 当 Ctor 为配置对象时,通过 Vue.extend 构造一个 Vue 的子类
  // 当 Ctor 是一个函数式,表示是异步组件,就不会执行 Vue.extend
  if (isObject(Ctor)) {
    Ctor = baseCtor.extend(Ctor)
  }
  // if at this stage it's not a constructor or an async component factory,
  // reject.
  if (typeof Ctor !== 'function') {
    if (process.env.NODE_ENV !== 'production') {
      warn(`Invalid Component definition: ${String(Ctor)}`, context)
    }
    return
  }
  // async component (异步组件)
  let asyncFactory
  if (isUndef(Ctor.cid)) {
    // 如果Ctor.cid为空,那么Ctor就是一个函数,表明这是一个异步组件
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
      // return a placeholder node for async component, which is rendered
      // as a comment node but preserves all the raw information for the node.
      // the information will be used for async server-rendering and hydration.
      // 为异步组件返回一个占位符节点,组件被渲染为注释节点,但保留了节点的所有原始信息,这些信息将用于异步服务器渲染 和 hydration
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
  // 节点的属性 JSON 字符串
  data = data || {}
  // 合并选项,在组件构造函数创建后应用全局混合的情况下解析构造函数选项
  // resolve constructor options in case global mixins are applied after
  // component constructor creation
  resolveConstructorOptions(Ctor)
  // 将组件的 v-model 转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调
  // transform component v-model data into props & events
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }
  // 提取 props 数据,得到 propsData 对象,propsData[key] = val
  // extract props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  // functional component (函数式组件)
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }
  // 提取事件侦听,因为这些侦听器需要被视为子组件侦听器,而不是DOM侦听器
  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // 替换为带有.native修饰符的侦听器,以便在父组件修补程序期间对其进行处理。
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn
  if (isTrue(Ctor.options.abstract)) {
    // 抽象组件只保留 props、listeners 和 slot
    // abstract components do not keep anything
    // other than props & listeners & slot
    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }
  // 安装组件钩子函数 init、prepatch、insert、destroy
  // install component management hooks onto the placeholder node
  installComponentHooks(data)
  // 通过 new VNode 实例化一个 vnode 并返回
  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
  )
  // Weex specific: invoke recycle-list optimized @render function for
  // extracting cell-slot template.
  // https://github.com/Hanks10100/weex-native-directive/tree/master/component
  /* istanbul ignore if */
  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }
  return vnode
}

从上面代码可以看出,对异步组件、函数式组件和普通组件都分别做了处理。就普通组件而言,

createComponent

方法大致做了这么几件事:

  • 通过

    Vue.extend

    构建子类的构造函数。
  • 安装组件钩子函数

    init



    prepatch



    insert



    destroy

  • 实例化 VNode,返回 VNode。


createComponent

内的逻辑有点复杂,我们按照几种组件的类别,先来看普通组件:



resolveConstructorOptions

// src/core/instance/init.js
/**
 * @description: 从组件构造函数中解析配置对象 options,并合并基类选项
 * @param {*} Ctor
 * @return {Object} options
 */
export function resolveConstructorOptions (Ctor: Class<Component>) {
  // 从实例构造函数上获取选项
  let options = Ctor.options
  if (Ctor.super) {
    // 存在基类,递归解析基类构造函数的选项
    const superOptions = resolveConstructorOptions(Ctor.super)
    // 缓存
    const cachedSuperOptions = Ctor.superOptions
    if (superOptions !== cachedSuperOptions) {
      // 说明基类的配置项发生了更改
      Ctor.superOptions = superOptions
      // 找到更改的选项
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // 如果存在被修改或增加的选项,则合并两个选项
      if (modifiedOptions) {
        // 将更改的选项和 extend 选项合并
        extend(Ctor.extendOptions, modifiedOptions)
      }
      // 将新的选项赋值给 options
      options = Ctor.options = mergeOptions(superOptions, Ctor.extendOptions)
      if (options.name) {
        options.components[options.name] = Ctor
      }
    }
  }
  return options
}



resolveModifiedOptions

// src/core/instance/init.js
/**
 * @description: 解析构造函数选项中后续被修改或者增加的选项
 * @param {*} Ctor
 * @return {*} modified 不一致的选项
 */
function resolveModifiedOptions (Ctor: Class<Component>): ?Object {
  let modified
  // 构造函数选项
  const latest = Ctor.options
  // 密封的构造函数选项,备份
  const sealed = Ctor.sealedOptions
  // 对比两个选项,记录不一致的选项
  for (const key in latest) {
    if (latest[key] !== sealed[key]) {
      if (!modified) modified = {}
      modified[key] = latest[key]
    }
  }
  return modified
}



transformModel

// src/core/vdom/create-component.js
/**
 * 将组件的 v-model 转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调
 * transform component v-model info (value and callback) into
 * prop and event handler respectively.
 */
function transformModel (options, data: any) {
  // model 的属性和事件,默认为 value 和 input
  const prop = (options.model && options.model.prop) || 'value'
  const event = (options.model && options.model.event) || 'input'
    // 在 data.attrs 对象上存储 v-model 的值
    ; (data.attrs || (data.attrs = {}))[prop] = data.model.value
  // 在 data.on 对象上存储 v-model 的事件
  const on = data.on || (data.on = {})
  // 已存在的事件回调函数
  const existing = on[event]
  // v-model 中事件对应的回调函数
  const callback = data.model.callback
  // 合并回调函数
  if (isDef(existing)) {
    if (
      Array.isArray(existing)
        ? existing.indexOf(callback) === -1
        : existing !== callback
    ) {
      on[event] = [callback].concat(existing)
    }
  } else {
    on[event] = callback
  }
}



extractPropsFromVNodeData

// src/core/vdom/helpers/extract-props.js
export function extractPropsFromVNodeData (
  data: VNodeData,
  Ctor: Class<Component>,
  tag?: string
): ?Object {
  // 只提取原始值,验证和默认值在子组件中处理
  // we are only extracting raw values here.
  // validation and default values are handled in the child
  // component itself.
  const propOptions = Ctor.options.props
  // 未定义 props
  if (isUndef(propOptions)) {
    return
  }
  const res = {}
  const { attrs, props } = data
  if (isDef(attrs) || isDef(props)) {
    // 遍历 propsOptions
    for (const key in propOptions) {
      // 将小驼峰形式的 key 转换为 xxx-xxx-xxx 形式
      const altKey = hyphenate(key)
      if (process.env.NODE_ENV !== 'production') {
      // 错误提示
        const keyInLowerCase = key.toLowerCase()
        if (
          key !== keyInLowerCase &&
          attrs && hasOwn(attrs, keyInLowerCase)
        ) {
          tip(
            `Prop "${keyInLowerCase}" is passed to component ` +
            `${formatComponentName(tag || Ctor)}, but the declared prop name is` +
            ` "${key}". ` +
            `Note that HTML attributes are case-insensitive and camelCased ` +
            `props need to use their kebab-case equivalents when using in-DOM ` +
            `templates. You should probably use "${altKey}" instead of "${key}".`
          )
        }
      }
      checkProp(res, props, key, altKey, true) ||
        checkProp(res, attrs, key, altKey, false)
    }
  }
  return res
}



checkProp

// src/core/vdom/helpers/extract-props.js
function checkProp (
  res: Object,
  hash: ?Object,
  key: string,
  altKey: string,
  preserve: boolean
): boolean {
  if (isDef(hash)) {
    // 判断 hash(props/attrs)对象中是否存在 key 或 altKey
    // 存在则设置给 res => res[key] = hash[key]
    if (hasOwn(hash, key)) {
      res[key] = hash[key]
      if (!preserve) {
        delete hash[key]
      }
      return true
    } else if (hasOwn(hash, altKey)) {
      res[key] = hash[altKey]
      if (!preserve) {
        delete hash[altKey]
      }
      return true
    }
  }
  return false
}



installComponentHooks


installComponentHooks

的作用就是把

componentVNodeHooks

的钩子函数合并到

data.hook

中,在合并过程,如果某个钩子存在,执行

mergeHook

函数做合并。在 VNode 执行

patch

的过程中会执行相关的钩子函数。

// src/core/vdom/create-component.js
/**
 * 在组件的 data 对象上设置 hook 对象,
 * hook 对象增加四个属性,init、prepatch、insert、destroy,
 * 负责组件的创建、更新、销毁
 */
function installComponentHooks (data: VNodeData) {
  const hooks = data.hook || (data.hook = {})
  // 遍历 hooksToMerge 数组,hooksToMerge = ['init', 'prepatch', 'insert' 'destroy']
  for (let i = 0; i < hooksToMerge.length; i++) {
    // 比如 key = init
    const key = hooksToMerge[i]
    // 从 data.hook 对象中获取 key 对应的方法
    const existing = hooks[key]
    // componentVNodeHooks 对象中 key 对象的方法
    const toMerge = componentVNodeHooks[key]
    // 合并用户传递的 hook 方法和框架自带的 hook 方法,其实就是分别执行两个方法
    if (existing !== toMerge && !(existing && existing._merged)) {
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
    }
  }
}

function mergeHook (f1: any, f2: any): Function {
  const merged = (a, b) => {
    // flow complains about extra args which is why we use any
    f1(a, b)
    f2(a, b)
  }
  merged._merged = true
  return merged
}



componentVNodeHooks

// src/core/vdom/create-component.js
// patch 期间在组件 VNode 上调用的内联钩子
// inline hooks to be invoked on component VNodes during patch
const componentVNodeHooks = {
  // 初始化
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // 被 keep-alive 包裹的组件
      // kept-alive components, treat as a patch
      const mountedNode: any = vnode // work around flow
      componentVNodeHooks.prepatch(mountedNode, mountedNode)
    } else {
      // 创建组件实例,即 new vnode.componentOptions.Ctor(options) => 得到 Vue 组件实例
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        activeInstance
      )
      // 调用 $mount 方法,进入挂载阶段。
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },
  // 更新 VNode
  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
    // 新的 VNode 配置项
    const options = vnode.componentOptions
    // 旧的 VNode 组件实例
    const child = vnode.componentInstance = oldVnode.componentInstance
    // 用新的更新旧的
    updateChildComponent(
      child,
      options.propsData, // updated props
      options.listeners, // updated listeners
      vnode, // new parent vnode
      options.children // new children
    )
  },
  // 执行组件的 mounted 钩子函数
  insert (vnode: MountedComponentVNode) {
    const { context, componentInstance } = vnode
    if (!componentInstance._isMounted) {
      componentInstance._isMounted = true
      callHook(componentInstance, 'mounted')
    }
    // 处理 keep-alive 组件的异常情况
    if (vnode.data.keepAlive) {
      if (context._isMounted) {
        // vue-router#1212
        // During updates, a kept-alive component's child components may
        // change, so directly walking the tree here may call activated hooks
        // on incorrect children. Instead we push them into a queue which will
        // be processed after the whole patch process ended.
        queueActivatedComponent(componentInstance)
      } else {
        activateChildComponent(componentInstance, true /* direct */)
      }
    }
  },
  // 销毁
  destroy (vnode: MountedComponentVNode) {
    // 获取组件实例
    const { componentInstance } = vnode
    // 已销毁的组件跳过
    if (!componentInstance._isDestroyed) {
      if (!vnode.data.keepAlive) {
        // 直接调用 $destroy 销毁组件
        componentInstance.$destroy()
      } else {
        // 被 keep-alive 包裹的组件
        // 让组件失活,不销毁组件实例,从而缓存组件的状态
        deactivateChildComponent(componentInstance, true /* direct */)
      }
    }
  }
}



createComponentInstanceForVnode

// src/core/vdom/create-component.js
// new vnode.componentOptions.Ctor(options) => 得到 Vue 组件实例 
export function createComponentInstanceForVnode (
  // we know it's MountedComponentVNode but flow doesn't
  vnode: any,
  // activeInstance in lifecycle state
  parent: any
): Component {
  const options: InternalComponentOptions = {
    _isComponent: true,
    _parentVnode: vnode,
    parent
  }
  // 检查内联模版渲染函数
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  // new VueComponent(options) => Vue 实例
  return new vnode.componentOptions.Ctor(options)
}

最后就是实例化 VNode,返回 VNode。除了普通组件,还有分支:异步组件和函数组件。下面看看异步组件和函数组件的流程:



异步组件

// src/core/vdom/create-component.js —— createComponent 方法中
// async component (异步组件)
let asyncFactory
if (isUndef(Ctor.cid)) {
  asyncFactory = Ctor
  Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
  if (Ctor === undefined) {
    // return a placeholder node for async component, which is rendered
    // as a comment node but preserves all the raw information for the node.
    // the information will be used for async server-rendering and hydration.
    // 为异步组件返回一个占位符节点,组件被渲染为注释节点,但保留了节点的所有原始信息,这些信息将用于异步服务器渲染 和 hydration
    return createAsyncPlaceholder(
      asyncFactory,
      data,
      context,
      children,       
      tag
    )
  }
}



resolveAsyncComponent

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
): Class<Component> | void {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }
  if (isDef(factory.resolved)) {
    return factory.resolved
  }
  // owner 实例收集容器,对同一个异步组件的引用不必多次解析,而是将当前使用该异步组件的实例收集起来,待到异步组件解析完毕,挨个通知渲染更新即可
  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }
  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
    return factory.loadingComp
  }
  //...
  // 这块代码有点长,我们在下面把它分为:普通异步组件、Promise异步组件,高级异步组件,分开来看
}

针对普通函数的情况,前面几个 if 判断可以忽略,它们是为高级组件所用,对于

factory.xxxxx

的判断,是考虑到多个地方同时初始化一个异步组件,那么它的实际加载应该只有一次。



createAsyncPlaceholder

// src/core/vdom/helpers/resolve-async-component.js
// 为异步组件返回一个占位符节点,组件被渲染为注释节点,但保留了节点的所有原始信息
export function createAsyncPlaceholder (
  factory: Function,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag: ?string
): VNode {
  const node = createEmptyVNode()
  node.asyncFactory = factory
  node.asyncMeta = { data, context, children, tag }
  return node
}



普通异步组件

Vue.component('async-example', function (resolve, reject) {
   // 这个特殊的 require 语法告诉 webpack
   // 自动将编译后的代码分割成不同的块,
   // 这些块将通过 Ajax 请求自动下载。
   require(['./my-async-component'], resolve)
   }
 )

Vue 允许将组件定义为一个工厂函数,动态的解析组件,Vue 只在组件需要渲染时触发工厂函数,并且把结果缓存起来,用于后面的再次渲染。

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
): Class<Component> | void {
 
  //普通异步组件第二次执行这里时会返回factory.resolved
  if (isDef(factory.resolved)) {
    return factory.resolved
  }
  // owner 实例收集容器,对同一个异步组件的引用不必多次解析,而是将当前使用该异步组件的实例收集起来,待到异步组件解析完毕,挨个通知渲染更新即可
  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }
 
  if (owner && !isDef(factory.owners)) {
    // owner 实例收集容器
    const owners = factory.owners = [owner]
    // sync 同步标识符,标识当前是同步还是异步
    let sync = true
    let timerLoading = null
    let timerTimeout = null
      ; (owner: any).$on('hook:destroyed', () => remove(owners, owner))
    const forceRender = (renderCompleted: boolean) => {
      // 依次调用该元素的 $forceUpdate()方法 该方法会强制渲染一次
      for (let i = 0, l = owners.length; i < l; i++) {
        (owners[i]: any).$forceUpdate()
      }
      // 组件更新之后 移除定时器,清空依赖
      if (renderCompleted) {
        owners.length = 0
        if (timerLoading !== null) {
          clearTimeout(timerLoading)
          timerLoading = null
        }
        if (timerTimeout !== null) {
          clearTimeout(timerTimeout)
          timerTimeout = null
        }
      }
    }
    // 定义一个 resolve 函数, once是一次性包装函数,保证传入的函数只执行一次(避免多次执行通知更新操作)
    const resolve = once((res: Object | Class<Component>) => {
      // 缓存 resolved
      factory.resolved = ensureCtor(res, baseCtor)
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if (!sync) {
        forceRender(true)
      } else {
        owners.length = 0
      }
    })
    //定义一个 reject 函数
    const reject = once(reason => {
      process.env.NODE_ENV !== 'production' && warn(
        `Failed to resolve async component: ${String(factory)}` +
        (reason ? `\nReason: ${reason}` : '')
      )
      if (isDef(factory.errorComp)) {
        factory.error = true
        forceRender(true)
      }
    })
    //执行 factory()函数
    const res = factory(resolve, reject)
    
    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}


resolveAsyncComponent

内部会定义一个

resolve



reject

函数,然后执行

factory()

函数,

factory()

就是我们在组件定义的函数,函数内会执行

require

函数,由于

require()

是个异步操作,所以

resolveAsyncComponent

就会返回

undefined

回到

createComponent

函数,由于返回的是

undefined

,则会执行

createAsyncPlaceholder

去创建一个注释节点占位符。

在下一个 tick 等

require

加载成功后就会执行

resolve

函数,也就是在

resolveAsyncComponent

内定义的

resolve

函数,

resolve

函数会将结果保存到工厂函数的

resolved

属性里。

再次重新渲染执行到

resolveAsyncComponent

的时候

factory.resolved

存在了,就直接返回。



Promise异步组件

Vue.component(
  'async-webpack-example',
  // 该 `import` 函数返回一个 `Promise` 对象。
  () => import('./my-async-component')
)

webpack 2+ 支持了异步加载的语法糖:

() => import('./my-async-component')

,当执行完

res = factory(resolve, reject)

,返回的值就是

import('./my-async-component')

的返回值,它是一个

Promise

对象。

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
): Class<Component> | void {

  // 组件第二次执行这里时会返回factory.resolved
  if (isDef(factory.resolved)) {
    return factory.resolved
  }

  if (owner && !isDef(factory.owners)) {
    // ...
    // 执行 factory()函数,
    // 返回一个含有then的对象
    const res = factory(resolve, reject)
    
    if (isObject(res)) {
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          // 如果 factory.resolved 不存在
          // 用 then 方法指定 resolve 和 reject 的回调函数
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
      // ...
      }
    }
    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}

当组件异步加载成功后,执行

resolve

,加载失败则执行

reject

,很好的配合 webpack 2+ 的异步加载组件的方式(Promise)。



高级异步组件

高级异步组件可以定义更多的状态,比如加载该组件的超时时间、加载过程中显式的组件、出错时显式的组件、延迟时间等。

const AsyncComp = () => ({
  component: import('./MyComp.vue'),
  loading: LoadingComp,
  error: ErrorComp,
  delay: 200,
  timeout: 3000
})
Vue.component('async-example', AsyncComp)

对于高级异步组件来说,他和promise()方法加载的逻辑是一样的,不同的是多了几个属性:

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
): Class<Component> | void {
  //...
  if (owner && !isDef(factory.owners)) {
    // ...
    //执行 factory()函数
    const res = factory(resolve, reject)
    if (isObject(res)) {
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
        // 高级异步组件的分支
        res.component.then(resolve, reject)
        if (isDef(res.error)) {
          // 失败时的模块
          factory.errorComp = ensureCtor(res.error, baseCtor)
        }
        if (isDef(res.loading)) {
          // 如果有设置加载时的模块
          factory.loadingComp = ensureCtor(res.loading, baseCtor)
          if (res.delay === 0) {
            // 如果等待时间为0
            factory.loading = true
          } else {
            timerLoading = setTimeout(() => {
              timerLoading = null
              if (isUndef(factory.resolved) && isUndef(factory.error)) {
                factory.loading = true
                forceRender(false)
              }
            }, res.delay || 200)
          }
        }
        if (isDef(res.timeout)) {
          // 超时时间
          timerTimeout = setTimeout(() => {
            timerTimeout = null
            if (isUndef(factory.resolved)) {
              reject(
                process.env.NODE_ENV !== 'production'
                  ? `timeout (${res.timeout}ms)`
                  : null
              )
            }
          }, res.timeout)
        }
      }
    }
    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}



函数式组件

// src/core/vdom/create-component.js —— createComponent 方法中
// functional component (函数式组件)
if (isTrue(Ctor.options.functional)) {
  return createFunctionalComponent(Ctor, propsData, data, context, children) 
}



createFunctionalComponent

// src/core/vdom/create-functional-component.js
export function createFunctionalComponent (
  Ctor: Class<Component>,
  propsData: ?Object,
  data: VNodeData,
  contextVm: Component,
  children: ?Array<VNode>
): VNode | Array<VNode> | void {
  //配置项
  const options = Ctor.options
  // props
  const props = {}
  // 组件本身的 props
  const propOptions = options.props
  // 设置函数式组件的 props 对象
  if (isDef(propOptions)) {
    // 函数式组件本身提供了 props 选项,则将 props.key 的值设置为组件上传递下来的对应 key 的值
    for (const key in propOptions) {
      props[key] = validateProp(key, propOptions, propsData || emptyObject)
    }
  } else {
    // 函数式组件没有提供 props 选项,则将组件上的 attribute 自动解析为 props
    if (isDef(data.attrs)) mergeProps(props, data.attrs)
    if (isDef(data.props)) mergeProps(props, data.props)
  }
  // 实例化函数式组件的渲染上下文
  const renderContext = new FunctionalRenderContext(
    data,
    props,
    children,
    contextVm,
    Ctor
  )
  // 调用 render 函数,生成 vnode,并给 render 函数传递 _c 和 渲染上下文
  const vnode = options.render.call(null, renderContext._c, renderContext)
  // 生成的 VNode 对象上加一些标记,表示该 VNode 是一个函数式组件生成的,最后返回 VNode
  if (vnode instanceof VNode) {
    return cloneAndMarkFunctionalResult(vnode, data, renderContext.parent, options, renderContext)
  } else if (Array.isArray(vnode)) {
    const vnodes = normalizeChildren(vnode) || []
    const res = new Array(vnodes.length)
    for (let i = 0; i < vnodes.length; i++) {
      res[i] = cloneAndMarkFunctionalResult(vnodes[i], data, renderContext.parent, options, renderContext)
    }
    return res
  }
}


createFunctionalComponent

在创建函数式组件的过程中,主要做了这么几件事:

  • 设置组件的

    props

    对象
  • 实例化组件,获取渲染上下文。
  • 调用

    render

    方法,生成 VNode.
  • 返回标记后的 VNode。

归根到底,使用

createElement

来创建元素的 VNode,

createComponent

来创建组件的 VNode,每个 VNode 有

children



children

包含子 VNode ,就形成了一个 VNode Tree。

_render

函数的执行就是为了获得到一颗 VNode 树。



相关链接


Vue源码解读(预):手写一个简易版Vue


Vue源码解读(一):准备工作


Vue源码解读(二):初始化和挂载


Vue源码解读(三):响应式原理


Vue源码解读(四):更新策略


Vue源码解读(五):render和VNode


Vue源码解读(六):update和patch


Vue源码解读(七):模板编译

如果觉得还凑合的话,给个赞吧!!!也可以来我的

个人博客

逛逛



版权声明:本文为qq_35500568原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。