细读Vue2.6.14 Core 源码(8): $createElement

906 阅读2分钟

细读Vue2.6.14 Core 源码(8): $createElement

    /**
        @date 2021-10-22
        @description 细读Vue2.6.14 Core 源码(8): $createElement
    */

壹(序)

在一个Vue项目的main文件中,一般都会这样去开始整个Vue项目的生命周期

new Vue({
  render: h => h(App),
}).$mount('#app')

其中的render很容易理解,就是去渲染App组件,而其中的h就是渲染的关键函数,这个h函数是在Vue.prototype._render中加上去的

vnode = render.call(vm._renderProxy, vm.$createElement)

vm.$createElement在initRender函数中进行声明的

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)

其中与 vm.$createElement 类似的还有一个 _c 函数,只是最后的一个参数不同,_c函数一般是在解析后的.vue文件中使用,比如一个简单的App.vue在解析后会变成

var render = function() {
  var _vm = this
  var _h = _vm.$createElement
  var _c = _vm._self._c || _h
  return _c(
    "div",
    { attrs: { id: "app" } },
    [_c("HelloWorld", { attrs: { msg: "Welcome to Your Vue.js App" } })],
    1
  )
}
var staticRenderFns = []
render._withStripped = true

export { render, staticRenderFns }

贰(createElement)

从 $createElement 和 _c 两个函数中可以看到, createElement才是重中之重

/**
 * 在 new Vue({ render: h => h(App) }).$mount('#app') => mountComponent => vm._render => vm.$createElement 会调用createElement
 * 其他组件的 $mount 在 componentVNodeHooks 中的 init 中调用
 * Vue将组件编译完成后,会生成render函数,此render函数依然在 vm._render 中调用
 * 编译生成的render函数不会用到 vm.$createElement 而是使用 vm._c(没有_c时会使用vm.$createElement)
 * vm.$createElement 与 vm._c 的区别在于 createElement 函数的最后一个参数 alwaysNormalize
 */
export function createElement(
  context: Component, // 当前操作的vm实例
  tag: any, // 标签名
  data: any, // 标签属性 ? 标签属性 : 子节点
  children: any, // 标签属性 ? 子节点 : undefined
  normalizationType: any, //
  alwaysNormalize: boolean
): VNode | Array<VNode> {
  // data是Array(说明没有标签属性,data是子节点
  // TODO: isPrimitive 的场景暂时不知道
  if (Array.isArray(data) || isPrimitive(data)) {
    // 这种情况下 children 基本是 undefined,就是相当于将 normalizationType 赋值为undefined
    normalizationType = children;
    // children就应该是data
    children = data;
    // data为undefined
    data = undefined;
  }
  // 根据 alwaysNormalize 设置 normalizationType
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE;
  }
  // 真实的是调用 _createElement 函数
  return _createElement(context, tag, data, children, normalizationType);
}

叁(_createElement)

createElement函数中,最终其实是调用_createElement函数,这里会生成标签或组件的VNode

// 从 text vnode 开始生成,再往外生成,直到最外层
// 比如 
/**
  从 text 开始生成,再往外生成,直到最外层
  比如 
  <h1>
    <span>hello world1 </span>
  </h1>
  是先生成 hello world1 的vnode(不是调用_createElement函数来生成)再生成span(会调用_createElement函数生成)
  再生成 h1
 */
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如果支持了响应式,则在非生产环境下报警告
    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
      );
    // 创建一个空vnode并返回
    return createEmptyVNode();
  }
  // object syntax in v-bind
  // 动态组件
  // <component is="HelloWorld"></component>编译过后的调用到此处时
  // tag就是'HelloWorld',data中会包含{ tag: component }
  if (isDef(data) && isDef(data.is)) {
    tag = data.is;
  }
  // 无tag的情况,返回空vnode
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode();
  }
  // warn against non-primitive key
  // key是一个object类型的情况,报警告
  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;
  }
  // 规范化的类型
  // SIMPLE_NORMALIZE 就是简单的把children打平
  // ALWAYS_NORMALIZE 不仅将children打平,同时还会将children中的primitive数据转化为text node
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children);
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children);
  }
  let vnode, ns;
  // 正常生成vnode时,只传一个标签名称,div/span等
  // 但是第一个$mount调用时,一般是<App></App>
  if (typeof tag === "string") {
    let Ctor;
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag);
    // 是否是保留tag,HTML的tag或svg
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      // 只允许 .native 在自建组件上使用
      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
      vnode = new VNode(
        config.parsePlatformTagName(tag), // 获取tagName
        data,
        children,
        undefined,
        undefined,
        context
      );
    } else if (
      (!data || !data.pre) &&
      isDef((Ctor = resolveAsset(context.$options, "components", tag)))
    ) {
      // 生成组件的 vnode
      vnode = createComponent(Ctor, data, context, children, tag);
    } else {
      // 其他情况
      vnode = new VNode(tag, data, children, undefined, undefined, context);
    }
  } else {
    // 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();
  }
}

肆(createComponent)

创建组件又需要一番操作

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
  }

  // Vue自身构造器
  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  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
  }

  // 异步组件
  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.
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
  // data
  data = data || {}

  // 解析构造函数options
  resolveConstructorOptions(Ctor)

  // 处理应用在组件上的 v-model
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

  // 获取props
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)

  // 函数组件
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  data.on = data.nativeOn

  // abstract 为 true
  if (isTrue(Ctor.options.abstract)) {
    // abstract components do not keep anything
    // other than props & listeners & slot

    // work around flow
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // install component management hooks onto the placeholder node
  installComponentHooks(data)

  // return a placeholder 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
  )

  if (__WEEX__ && isRecyclableComponent(vnode)) {
    return renderRecyclableComponentTemplate(vnode)
  }

  return vnode
}

终(导航)

细读Vue2.6.14 Core 源码(1): 入口

细读Vue2.6.14 Core 源码(2): After Vue

细读Vue2.6.14 Core 源码(3): initGlobalAPI

细读Vue2.6.14 Core 源码(4): _init

细读Vue2.6.14 Core 源码(5): initState

细读Vue2.6.14 Core 源码(6): defineReactive

细读Vue2.6.14 Core 源码(7): $mount

细读Vue2.6.14 Core 源码(8): $createElement