从 vue 源码看问题 —— vue 中的 render helper 是什么?(二)

简介: 从 vue 源码看问题 —— vue 中的 render helper 是什么?

createElement() 方法

文件位置:src\core\vdom\create-element.js

/*
 生成组件或普通标签的 vnode,一个包装函数
 wrapper function for providing a more flexible interface
 without getting yelled at by flow
*/
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

/**
 * 生成 vnode,
 *   1、平台保留标签和未知元素执行 new Vnode() 生成 vnode
 *   2、组件执行 createComponent 生成 vnode
 *     2.1 函数式组件执行自己的 render 函数生成 VNode
 *     2.2 普通组件则实例化一个 VNode,并且在其 data.hook 对象上设置 4 个方法,
 *         在组件的 patch 阶段会被调用,从而进入子组件的实例化、挂载阶段,直至完成渲染
 * @param {*} context 上下文
 * @param {*} tag 标签
 * @param {*} data 属性 JSON 字符串
 * @param {*} children 子节点数组
 * @param {*} normalizationType 节点规范化类型
 * @returns VNode or Array<VNode>
 */
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__)) {
    // 属性不能是一个响应式对象
    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
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    /*
     动态组件的 is 属性是一个假值时,tag 为 false,则返回一个空节点的 VNode
     如:<component :is="false"></component>
     in case of component :is set to falsy value
    */
    return createEmptyVNode()
  }
  /*
   检测唯一键 key,只能是 字符串 或者 数字
   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') {
    /*
     标签是字符串时,该标签有三种可能:
      1、平台保留标签
      2、自定义组件
      3、不知名标签
    */
    let Ctor
    // 命名空间
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      /*
        tag 是平台原生标签
        platform built-in elements
      */
      if (process.env.NODE_ENV !== 'production' && isDef(data) && isDef(data.nativeOn) && data.tag !== 'component') {
        /*
         v-on 指令的 .native 只在组件上生效,如:
          <comp @click="outClick"></comp> --> 点击组件无法触发 outClick 方法
          <comp @click.native="outClick"></comp> --> 点击组件可以触发 outClick 方法
        */
        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), data, children,
        undefined, undefined, context
      )
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      /* 
       tag 是一个自定义组件
       - 在 this.$options.components 对象中找到指定标签名称的组件构造函数,
         创建组件的 VNode
       - 函数式组件直接执行其 render 函数生成 VNode,
       - 普通组件则实例化一个 VNode,并且在其 data.hook 对象上设置了 4 个方法,
         在组件的 patch 阶段会被调用
       从而进入子组件的实例化、挂载阶段,直至完成渲染
       component
      */
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // 不知名的一个标签,但也生成 VNode,因为考虑到在运行时可能会给一个合适的名字空间
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    /*
     tag 为非字符串,比如可能是一个组件的配置对象或者是一个组件的构造函数
     direct component options / constructor
    */ 
    vnode = createComponent(tag, data, context, children)
  }
  // 返回组件的 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() 方法

文件位置:src\core\vdom\create-component.js

/**
 * 创建组件的 VNode,
 *   1、函数式组件通过执行其 render 方法生成组件的 VNode
 *   2、普通组件通过 new VNode() 生成其 VNode,
 *      但是普通组件有一个重要操作是在 data.hook 对象上设置了四个钩子函数,
 *      分别是 init、prepatch、insert、destroy,在组件的 patch 阶段会被调用,
 *      比如 init 方法,调用时会进入子组件实例的创建挂载阶段,直到完成渲染
 * @param {*} Ctor 组件构造函数
 * @param {*} data 属性组成的 JSON 字符串
 * @param {*} context 上下文
 * @param {*} children 子节点数组
 * @param {*} tag 标签名
 * @returns VNode or Array<VNode>
 */
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  // 组件构造函数不存在,直接结束,Ctor === undefined || Ctor === null
  if (isUndef(Ctor)) {
    return
  }
  // Vue.extend
  const baseCtor = context.$options._base
  // plain options object: turn it into a constructor
  // 当 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.
  // 如果到这个为止,Ctor 仍然不是一个函数,则表示这是一个无效的组件定义
  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)) {
    asyncFactory = Ctor
    Ctor = resolveAsyncComponent(asyncFactory, baseCtor)
    if (Ctor === undefined) {
     /*
      为异步组件返回一个占位符节点,组件被渲染为注释节点,
      但保留了节点的所有原始信息,这些信息将用于 异步服务器渲染 和 hydration
     */ 
      return createAsyncPlaceholder(
        asyncFactory,
        data,
        context,
        children,
        tag
      )
    }
  }
  // 节点的属性 JSON 字符串
  data = data || {}
  /*
    这里其实就是组件做选项合并的地方,即编译器将组件编译为渲染函数,
    渲染时执行 render 函数,然后执行其中的 _c,就会走到这里了
    解析构造函数选项,并合基类选项,以防止在组件构造函数创建后应用全局混入
  */
  resolveConstructorOptions(Ctor)
  // transform component v-model data into props & events
  // 将组件的 v-model 的信息(值和回调)转换为 data.attrs 对象的属性、值和 data.on 对象上的事件、回调
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }
  /*
    extract props
    提取 props 数据,得到 propsData 对象,propsData[key] = val
    以组件 props 配置中的属性为 key,父组件中对应的数据为 value
  */
  const propsData = extractPropsFromVNodeData(data, Ctor, tag)
  // functional component ——  函数式组件
  if (isTrue(Ctor.options.functional)) {
     /**
      执行函数式组件的 render 函数生成组件的 VNode :
        1、设置组件的 props 对象
        2、设置函数式组件的渲染上下文,传递给函数式组件的 render 函数
        3、调用函数式组件的 render 函数生成 vnode
     */
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }
  // extract listeners, since these needs to be treated as
  // child component listeners instead of DOM listeners
  // 获取事件监听器对象 data.on,因为这些监听器需要作为子组件监听器处理,而不是 DOM 监听器
  const listeners = data.on
  // replace with listeners with .native modifier
  // so it gets processed during parent component patch.
  // 将带有 .native 修饰符的事件对象赋值给 data.on
  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
    }
  }
  /**
    在组件的 data 对象上设置 hook 对象,
    hook 对象增加四个属性,init、prepatch、insert、destroy,
    负责组件的创建、更新、销毁,这些方法在组件的 patch 阶段会被调用
    install component management hooks onto the placeholder node
   */
  installComponentHooks(data)
  // return a placeholder vnode
  const name = Ctor.options.name || tag
  // 实例化组件的 VNode,对于普通组件的标签名会比较特殊,vue-component-${cid}-${name}
  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
}
复制代码

resolveConstructorOptions() 方法

文件位置:src\core\vdom\create-component.js

// 从构造函数上解析配置对象
export function resolveConstructorOptions (Ctor: Class<Component>) {
  let options = Ctor.options
  // 如果构造函数的 super 属性存在,证明还有基类,此时需要递归进行处理
  if (Ctor.super) {
    const superOptions = resolveConstructorOptions(Ctor.super)
    // 缓存
    const cachedSuperOptions = Ctor.superOptions
    // 若缓存的配置和基类的配置不一致,说明配置已发生更改
    if (superOptions !== cachedSuperOptions) {
      // super option changed,
      // need to resolve new options.
      Ctor.superOptions = superOptions
      // check if there are any late-modified/attached options (#4976)
      // 获取发生变更的配置项
      const modifiedOptions = resolveModifiedOptions(Ctor)
      // update base extend options
      if (modifiedOptions) {
        // 将更改的配置项和 extent 选项进行合并
        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

/**
 * 解析构造函数选项中后续被修改或者增加的选项
 */
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
  }
}


目录
相关文章
|
21小时前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的画师约稿平台附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的画师约稿平台附带文章源码部署视频讲解等
22 8
|
21小时前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的档案管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的档案管理系统附带文章源码部署视频讲解等
26 8
|
21小时前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的儿童性教育网站附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的儿童性教育网站附带文章源码部署视频讲解等
13 5
|
21小时前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的高校学生饮食推荐系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的高校学生饮食推荐系统附带文章源码部署视频讲解等
12 0
|
21小时前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的高校毕业与学位资格审核系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的高校毕业与学位资格审核系统附带文章源码部署视频讲解等
14 2
|
21小时前
|
JavaScript Java 测试技术
基于ssm+vue.js+uniapp小程序的智慧养老院管理系统附带文章和源代码部署视频讲解等
基于ssm+vue.js+uniapp小程序的智慧养老院管理系统附带文章和源代码部署视频讲解等
5 0
基于ssm+vue.js+uniapp小程序的智慧养老院管理系统附带文章和源代码部署视频讲解等
|
22小时前
|
JavaScript Java 测试技术
基于springboot+vue.js+uniapp小程序的电子病历管理系统附带文章源码部署视频讲解等
基于springboot+vue.js+uniapp小程序的电子病历管理系统附带文章源码部署视频讲解等
7 0
|
2天前
|
JavaScript API
vue组合式和选项式
vue组合式和选项式
4 2
|
5天前
|
JavaScript 程序员 网络架构
vue路由从入门到进阶 --- 声明式导航详细教程
vue路由从入门到进阶 --- 声明式导航详细教程
vue路由从入门到进阶 --- 声明式导航详细教程
|
5天前
|
资源调度 JavaScript UED
vue路由的基础知识总结,vueRouter插件的安装与使用
vue路由的基础知识总结,vueRouter插件的安装与使用