关于javascript:Vue源码解读五render和VNode

44次阅读

共计 32630 个字符,预计需要花费 82 分钟才能阅读完成。

Vue 2.0 相比 Vue 1.0 最大的降级就是利用了虚构 DOM。在 Vue 1.0 中视图的更新是纯响应式的。在进行响应式初始化的时候,一个响应式数据 key 会创立一个对应的 dep,这个 key 在模板中被援用几次就会创立几个 watcher。也就是一个 key 对应一个 depdep 内治理一个或者多个 watcher。因为 watcherDOM 是一对一的关系,更新时,能够明确的对某个 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.

这里还有一个值得注意的中央就是通过 normalizeChildrensimpleNormalizeChildren 办法对子节点进行规范化,使其每个节点都是 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 构建子类的构造函数。
  • 装置组件钩子函数 initprepatchinsertdestroy
  • 实例化 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 外部会定义一个 resolvereject 函数,而后执行 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 有 childrenchildren 蕴含子 VNode,就造成了一个 VNode Tree。_render 函数的执行就是为了取得到一颗 VNode 树。

相干链接

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

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

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

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

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

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

[Vue 源码解读(六):update 和 patch(待续)]()

[Vue 源码解读(七):模板编译(待续)]()

如果感觉还对付的话,给个赞吧!!!也能够来我的集体博客逛逛 https://www.mingme.net/

正文完
 0