Vue 2.6 源码剖析-组件化

组件化回顾

Vue 的核心组成只有 数据绑定 和 组件化。

  • 一个 Vue 组件就是一个拥有预定义选项的一个 Vue 实例。
  • 一个组件可以组成页面上一个功能完毕的区域,组件可以包含脚本、样式、模板。
  • 组件化可以方便的把页面拆分成多个可重用的组件。
    • 使用组件可以重用页面中的某个区域。
    • 组件是可以嵌套的,搭建页面就像搭积木。

组件注册方式

  • 全局组件 Vue.component
    • 在全局可以使用
  • 局部组件
    • 在当前注册的范围中使用

全局组件的注册过程

Vue.component 注册全局组件

Vue.component 是静态方法,是在 src\core\global-api\index.js 中 的 initAssetRegisters 函数中注册的。

// src\core\global-api\index.js
// 记录 Vue 构造函数到 _base(留意,后面会用到)
Vue.options._base = Vue

// ...

// 注册 Vue.directive()、Vue.component()、Vue.filter()
initAssetRegisters(Vue)
initAssetRegisters

initAssetRegisters 接收 Vue 的构造函数作为参数。

内部遍历ASSET_TYPES([directive, component, filter]),定义对应的三个Vue的静态方法。

它们的实现都是类似的。

  • 如果没有第二个参数,则获取全局的内容
  • 如果有第二个参数,分别作响应的处理,最终记录到全局,并返回。

通过源码得知,Vue.component 主要在全局记录了组件的构造函数并返回。

如果它的第二个参数是对象(组件配置选项)不是函数(构造函数),就会调用 Vue.extend 将配置转化为组件的构造函数。

// src\core\global-api\assets.js
export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  // 遍历 ASSET_TYPES 数组,为 Vue 定义相应方法
  // ASSET_TYPES: [directive, component, filter]
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string,
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
        // 获取
        // 如果传入了第二个参数,说明是获取之前定义的全局内容(组件、指令、过滤器)
        return this.options[type + 's'][id]
      } else {
        // 创建
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          // 如果是开发环境,验证id(名称)是否合法
          // 不合法会报警告
          validateComponentName(id)
        }
        
        // isPlainObject 通过 toString() 判断是否是 原始Object对象

        // 组件处理
        // 如果type是组件,并且第二个参数是对象
        // Vue.component('comp', {template: ''})
        if (type === 'component' && isPlainObject(definition)) {
          // 先获取配置中的name作为名称,没有则取id
          definition.name = definition.name || id
          // Vue.options._base 存储的就是 Vue 构造函数
          // 这里调用 Vue.extend 把组件配置definition  转换为 组件的构造函数
          definition = this.options._base.extend(definition)
        }

        // 指令处理
        if (type === 'directive' && typeof definition === 'function') {
          // 如果配置是函数,就包装一下
          definition = { bind: definition, update: definition }
        }

        // 将配置记录到对应全局中
        //   如果是过滤器,不做处理,直接记录并返回
        //   如果组件第二个参数是函数(组件构造函数),不做处理,直接记录并返回
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

Vue.extend

在Vue.component(id, options) 中,如果 options 是对象(组件配置选项),不是函数(构造函数),则调用 Vue.extend 把该组件的选项对象,转化成 Vue 构造函数的子类,也就是对应组件的构造函数。

所以组件其实也是一个Vue实例。

开发自定义组件的过程中,可能会用到 Vue.extend 方法。

它是 Vue 的静态方法,在src\core\global-api\extend.js中定义。

  • 它内部就是基于传入的组件对象,创建组件的构造函数。
  • 组件的构造函数继承自Vue构造函数
    • 所以组件对象拥有和Vue实例一样的成员
// src\core\global-api\extend.js
export function initExtend (Vue: GlobalAPI) {
  /**
   * Each instance constructor, including Vue, has a unique
   * cid. This enables us to create wrapped "child
   * constructors" for prototypal inheritance and cache them.
   * 每个实例构造函数(包括Vue)都有一个唯一的cid。
   * 这使我们能够通过原型继承,创建一个包裹的“子构造函数”,并缓存它们
   */
  Vue.cid = 0
  let cid = 1

  /**
   * Class inheritance
   */
  Vue.extend = function (extendOptions: Object): Function {
    // extendOptions 是组件的选项对象
    // 之后会被合并到构造函数的选项对象中(this.options)
    extendOptions = extendOptions || {}
    // 获取构造函数
    //   this 是 Vue 构造函数
    //   或者是 继承的子构造函数,也就是组件的构造函数
    //   因为 组件的构造函数 也拥有 extend 方法
    const Super = this
    const SuperId = Super.cid

    // 先判断是否可以从缓存中获取
    // 判断是否有 _Ctor 属性,如果没有初始化一个空对象
    // _Ctor 存储缓存的构造函数
    const cachedCtors = extendOptions._Ctor || (extendOptions._Ctor = {})
    if (cachedCtors[SuperId]) {
      // 如果有缓存,直接返回
      return cachedCtors[SuperId]
    }

    // 获取组件的名称
    const name = extendOptions.name || Super.options.name
    if (process.env.NODE_ENV !== 'production' && name) {
      // 如果是开发环境,使用正则验证组件是否合法
      validateComponentName(name)
    }

    // 初始化创建一个组件的构造函数
    const Sub = function VueComponent (options) {
      // 内部调用 _init() 初始化
      // 下面改造Sub的原型,使其继承自Vue,所以它也可以调用_init方法
      this._init(options)
    }
    // 改造构造函数的原型,使其继承自 Vue
    // 所以所有的 Vue 组件都继承自 Vue
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub

    // 设置cid,后面缓存的时候要用
    Sub.cid = cid++
    // 合并Super的选项 和 传入的选项,作为当前构造函数的选项
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // 下面就是将 Vue 的成员拷贝到 Sub 组件中

    // For props and computed properties, we define the proxy getters on
    // the Vue instances at extension time, on the extended prototype. This
    // avoids Object.defineProperty calls for each instance created.
    // 初始化子组件的 props computed
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    // 然后继承Super的静态方法
    Sub.extend = Super.extend
    Sub.mixin = Super.mixin
    Sub.use = Super.use

    // create asset registers, so extended classes
    // can have their private assets too.
    ASSET_TYPES.forEach(function (type) {
      Sub[type] = Super[type]
    })
    // enable recursive self-lookup
    // 把组件构造函数(自己)保存到 [Vue/Sub].options.components 对象
    if (name) {
      Sub.options.components[name] = Sub
    }

    // keep a reference to the super options at extension time.
    // later at instantiation we can check if Super's options have
    // been updated.
    Sub.superOptions = Super.options
    Sub.extendOptions = extendOptions
    Sub.sealedOptions = extend({}, Sub.options)

    // cache constructor
    // 把组件的构造函数缓存到 options._Ctor
    cachedCtors[SuperId] = Sub
    // 返回构造函数
    return Sub
  }
}

从源码中得知,生成组件构造函数的时候,组件会合并父组件的options选项,这也是组件 data 必须设置为函数的原因:

  • 如果页面中在多个地方使用了同一个组件,并且组件的 data 不是返回对象的函数,而是一个对象
  • 那创建组件实例的时候,它的每个实例的 data 都指向同一个对象(同一个地址)
  • 当修改data中属性的时候,每个组件就都会被影响到

而如果data定义的是一个函数,实例化组件时,就会将data定义为调用函数获取的返回值,这样每个组件的实例都有自己的缓存,互相之间不会影响。

Vue内部已经做了判断,防止这种情况出现。如果想测试,可以注释源码中的判断。

调试全局组件注册过程

<div id="app"></div>
<script src="../../dist/vue.js"></script>
<script>
  const Comp = Vue.component('comp', {
    template: '<div>I am a comp</div>'
  })
  const vm = new Vue({
    el: "#app",
    render(h) {
      // Vue CLI的使用方式
      return h(Comp)
    }
  });
</script>

断点位置:

  • src\core\global-api\assets.js 中定义 Vue.component 的位置

组件的创建过程

回顾首次渲染过程

  • Vue 构造函数
  • this._init()
  • this.$mount()
  • mountComponent()
  • new Watcher() 渲染 Watcher
  • updateComponent()
  • vm._render() -> createElement()
  • vm._update()

现在看 createElement 中创建组件的过程。

createElement

src\core\vdom\create-element.js 中定义了createElement。

它内部最终调用了 _createElement。

_createElement 中判断当前为组件时,调用 createComponent 创建组件对应的 VNode 对象。

// src\core\vdom\create-element.js
/**
 * @param {*} context // Vue 实例 或 当前组件实例
 * @param {*} tag 标签的名称(string) 或 组件(组件构造函数[Class | Function] | 组件选项对象)
 * @param {*} data 创建VNode时需要的数据
 * @param {*} children 子节点数组
 * @param {*} normalizationType 如何处理子节点数组
 * @return 最终创建并返回 VNode 对象
 */
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  // ...
  if (typeof tag === 'string') {
    // 如果tag是字符串
    // ...
  } else {
    // 如果tag不是字符串,那它应该是一个组件
    // createComponent创建组件对应的Vnode
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  //...
}

createComponent

createComponent 最终把组件转化成了 vnode 对象。

内部还初始化了4个钩子函数。

在 init 钩子函数中,创建了 组件实例。

init 钩子函数是在 patch 的过程中调用的。

// src\core\vdom\create-component.js
/**
 * @param {*} Ctor 组件构造函数 或 选项对象
 * @param {*} data 创建 VNode 需要的数据
 * @param {*} context 上下文:Vue实例或当前组件实例
 * @param {*} children 子节点数组
 * @param {*} tag 标签名称
 * @return 创建好的 Vnode 对象
 */
export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  // 验证Ctor是否有效
  if (isUndef(Ctor)) {
    return
  }

  // 首先获取 Vue 构造函数
  // _init 中会把Vue 构造函数中的选项合并到 Vue 实例的选项中
  // 所以这里可以通过 实例的选项,获取_base,即Vue构造函数
  const baseCtor = context.$options._base

  // plain options object: turn it into a constructor
  // 如果Ctor时对象,则是选项对象
  if (isObject(Ctor)) {
    // 调用 Vue.extend 把选项对象转换成组件的构造函数
    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
  // 如果 Ctor 中没有cid,那它就是异步组件(暂不关心)
  // 通过extend创建的构造函数肯定有cid
  if (isUndef(Ctor.cid)) {
    // ...
  }

  data = data || {}

  // 当组件构造函数创建完毕后
  // 合并当前组件选项和通过Vue.mixin混入的选项
  resolveConstructorOptions(Ctor)

  // transform component v-model data into props & events
  // 处理组件上的 v-model 指令
  if (isDef(data.model)) {
    transformModel(Ctor.options, data)
  }

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

  // functional component
  if (isTrue(Ctor.options.functional)) {
    return createFunctionalComponent(Ctor, propsData, data, context, children)
  }

  const listeners = data.on
  data.on = data.nativeOn

  if (isTrue(Ctor.options.abstract)) {
    const slot = data.slot
    data = {}
    if (slot) {
      data.slot = slot
    }
  }

  // 安装组件的钩子函数
  // 组件中默认的钩子函数有4个:init/prepatch/insert/destroy
  // 组件对象真正创建实在init钩子函数中
  installComponentHooks(data)

  // return a placeholder vnode
  // 获取组件的名称
  const name = Ctor.options.name || tag
  // 创建组件对应的 VNode 对象(这是createComponent函数的核心)
  //   组件的名称是以`vue-component-`为前缀,然后拼接上组件的cid,如果有name属性再拼接name
  //   然后传入 Vnode 的data
  const vnode = new VNode(
    `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
    data, undefined, undefined, undefined, context,

    // 这是vnode 的 componentOptions 对象
    //   组件的 init 钩子函数接收 vnode 作为参数
    //   内部调用 createComponentInstanceForVnode
    //   通过 new vnode.componentOptions.Ctor(options) 创建了组件的实例
    { Ctor, propsData, listeners, tag, children },

    asyncFactory
  )

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

  // 最后返回 vnode 对象
  return vnode
}
installComponentHooks
// src\core\vdom\create-component.js
const hooksToMerge = Object.keys(componentVNodeHooks)
//...
function installComponentHooks (data: VNodeData) {
  // 获取 data.hook
  // data.hook 是用户传入的组件钩子函数
  const hooks = data.hook || (data.hook = {})
  // 遍历hooksToMerge
  for (let i = 0; i < hooksToMerge.length; i++) {
    const key = hooksToMerge[i]
    // 获取用户传入的钩子函数
    const existing = hooks[key]
    // 获取 componentVNodeHooks 中定义的钩子函数
    const toMerge = componentVNodeHooks[key]
    if (existing !== toMerge && !(existing && existing._merged)) {
      // 通过 mergeHook 把两个钩子函数合并到一起
      hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
    }
  }
}

// 合并两个钩子函数
function mergeHook (f1: any, f2: any): Function {
  // 创建一个函数
  //   内部先调用内部(componentVNodeHooks)定义的钩子函数
  //   然后调用用户传入的钩子函数
  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
// componentVNodeHooks 定义了组件默认的4个钩子函数
const componentVNodeHooks = {
  // 组件对象真正创建实在init钩子函数中
  init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
    if (
      vnode.componentInstance &&
      !vnode.componentInstance._isDestroyed &&
      vnode.data.keepAlive
    ) {
      // 这是处理keep-alive的情况(暂不关心)
      // ...
    } else {
      // 调用 createComponentInstanceForVnode 创建组件的实例
      // 并把创建好的组件实例,存入到 vnode.componentInstance
      const child = vnode.componentInstance = createComponentInstanceForVnode(
        vnode,
        // activeInstance:当前组件的父组件对象
        activeInstance
      )
      // 组件没有el,所以 _init 中不会调用 $mount
      // 组件是在这里调用 $mount 的
      child.$mount(hydrating ? vnode.elm : undefined, hydrating)
    }
  },

  prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {/*...*/},

  insert (vnode: MountedComponentVNode) {/*...*/},

  destroy (vnode: MountedComponentVNode) {/*...*/}
}
createComponentInstanceForVnode
export function createComponentInstanceForVnode (
  vnode: any, // we know it's MountedComponentVNode but flow doesn't
  parent: any, // activeInstance in lifecycle state
): Component {
  // 创建 options 对象
  const options: InternalComponentOptions = {
    _isComponent: true, // 标记当前是组件
    _parentVnode: vnode, // 当前创建好的 vnode 对象
    parent // 当前组件的父组件对象
  }
  // check inline-template render functions
  // 处理 inline-template(暂不关心)
  // <comp inlin-template> xxx </comp>
  const inlineTemplate = vnode.data.inlineTemplate
  if (isDef(inlineTemplate)) {
    options.render = inlineTemplate.render
    options.staticRenderFns = inlineTemplate.staticRenderFns
  }
  // 通过 new 组件构造函数,创建组件实例,传入了options
  return new vnode.componentOptions.Ctor(options)
}

组件 patch 的过程

组件在 createElement 中被转化成了 vnode,但它是在 init 钩子函数中最终被实例化。

init 钩子函数是在 patch 中调用的。

src\core\vdom\patch.js 中定义了patch 函数。

patch 内部最终会调用 createElm 把 vnode 转化成真实DOM,挂载到DOM树。

上面已经介绍了组件是如何转化成 vnode 的。

现在查看 createElm 中是如何处理组件的 vnode 的。

createElm 中会调用 createComponent 来处理组件的 vnode。

// src\core\vdom\patch.js
// 调用 createComponent 处理组件的情况
if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  return
}

createComponent

createComponent 内部获取组件 init 钩子函数,并调用。

init 通过组件构造函数初始化组件实例。

构造函数中会调用 _init 函数进行初始化。

_init 函数就是 Vue 原型的 实例方法,此时父组件已经创建完成,当前是子组件在调用。

所以组件的创建过程是,先创建父组件,再创建子组件。

_init 中会判断选项的 _isComponent,组件已经把它设置为true,接着会调用 initInternalComponent 合并选项。

// src\core\vdom\patch.js
function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  let i = vnode.data
  if (isDef(i)) {
    const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
    // 获取 vnode.data.hook 也就是钩子函数
    // 然后获取 init 钩子函数
    if (isDef(i = i.hook) && isDef(i = i.init)) {
      // 调用 init 钩子函数
      // 传入两个参数:
      //   vnode 自身
      //   false
      // init 实例化组件,并把实例对象存入 vnode.componentInstance
      i(vnode, false /* hydrating */)
    }
    // after calling the init hook, if the vnode is a child component
    // it should've created a child instance and mounted it. the child
    // component also has set the placeholder vnode's elm.
    // in that case we can just return the element and be done.
    if (isDef(vnode.componentInstance)) {
      // initComponent
      //   初始化 vnode.elm
      //   触发 vnode 的create 钩子函数 初始化属性/时事件/样式等
      //   触发 组件 的 create 钩子函数(内置create和用户定义的create合并后的钩子函数)
      initComponent(vnode, insertedVnodeQueue)
      // 调用 insert 把组件插入到 父组件中
      insert(parentElm, vnode.elm, refElm)
      if (isTrue(isReactivated)) {
        reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
      }
      return true
    }
  }
}
// src\core\instance\init.js
Vue.prototype._init = function (options?: Object) {
  // ...
  // merge options
  // 合并 options
  // 将用户传入的 options 和 Vue 构造函数中初始化的options 进行合并
  if (options && options._isComponent) {
    // optimize internal component instantiation
    // since dynamic options merging is pretty slow, and none of the
    // internal component options needs special treatment.
    initInternalComponent(vm, options)
  } else {
  	// ...
  }
  // ...
  // 初始化和生命周期相关的一些属性
  // $parent/$root/$children/$refs 等
  // 还记录了组件之间的父子关系
  initLifecycle(vm)
  
  //...
  
  // 最后调用 $mount 方法挂载
  // 子组件的选项中没有 el ,所以子组件不会调用 $mount
  //   子组件是在 init 钩子函数中 createComponentInstanceForVnode 创建完组件实例后调用$mount的
  if (vm.$options.el) {
    vm.$mount(vm.$options.el)
  }
}

initInternalComponent

// src\core\instance\init.js
export function initInternalComponent (vm: Component, options: InternalComponentOptions) {
  // 先基于当前vm构造函数的options,创建当前实例的 options
  const opts = vm.$options = Object.create(vm.constructor.options)
  // doing this because it's faster than dynamic enumeration.
  // 获取 createComponentInstanceForVnode 存储的 _parentVnode
  // _parentVnode 存储的就是当前组件创建的vnode
  const parentVnode = options._parentVnode
  // 然后把 parentVnode 和 当前组件的父组件对象(parent)记录到选项中
  opts.parent = options.parent
  opts._parentVnode = parentVnode

  // 下面是记录其他选项(暂不关心)
  // ...
}

initLifecycle

// src\core\instance\lifecycle.js
export function initLifecycle (vm: Component) {
  const options = vm.$options

  // locate first non-abstract parent
  // 找到当前组件的父组件
  let parent = options.parent
  if (parent && !options.abstract) {
    while (parent.$options.abstract && parent.$parent) {
      parent = parent.$parent
    }
    // 将当前组件添加到父组件的$children中
    // 在父组件中记录子组件
    parent.$children.push(vm)
  }

  // 记录parent,至此建立了父子组件的关系
  // parent 在 调用 createComponentInstanceForVnode 是作为参数(activeInstance)传入
  // activeInstance 在 lifecycleMixin 中定义的 _update 方法中被初始化
  vm.$parent = parent
  //...
}

export function lifecycleMixin (Vue: Class<Component>) {
  // _update 方法的作用是把 VNode 渲染成真实的 DOM
  // 首次渲染会调用,数据更新会调用
  Vue.prototype._update = function (vnode: VNode, hydrating?: boolean) {
    const vm: Component = this
    const prevEl = vm.$el
    // 获取之前处理的 vnode 对象
    const prevVnode = vm._vnode
    // setActiveInstance 把当前 vm 实例缓存起来,传入 activeInstance
    // 父组件会先调用 _update 方法
    // 然后调用 patch,在patch中创建子组件
    // 创建子组件时再调用 _update 方法
    const restoreActiveInstance = setActiveInstance(vm)
    // 更新_vnode为新vnode
    vm._vnode = vnode
    // Vue.prototype.__patch__ is injected in entry points
    // Vue原型上的__patch__方法是在入口中被注入进来的
    // based on the rendering backend used.、
    // 核心部分:调用__patch__,把虚拟DOM转换成真实DOM,最终挂载到 $el
    if (!prevVnode) {
      // 如果不存在处理过的 vnode,说明是首次渲染
      // initial render
      vm.$el = vm.__patch__(vm.$el, vnode, hydrating, false /* removeOnly */)
    } else {
      // 数据变更渲染
      // updates
      vm.$el = vm.__patch__(prevVnode, vnode)
    }
    // 调用完patch方法后,还原activeInstance
    restoreActiveInstance()
    // ...
  }
}

setActiveInstance

// src\core\instance\lifecycle.js
export function setActiveInstance(vm: Component) {
  // 先把当前的 activeInstance 记录到常量中
  const prevActiveInstance = activeInstance
  // 然后重新赋值
  activeInstance = vm
  // 返回一个还原 activeInstance 的函数
  // 目的是:解决组件嵌套的问题
  return () => {
    activeInstance = prevActiveInstance
  }
}

src\core\vdom\create-component.js 中定义组件init 钩子函数,中调用 createComponentInstanceForVnode 时传入了 activeInstance。

该文件中从 src\core\instance\lifecycle.js 导入了 activeInstance。

总结

在这里插入图片描述

整个跳转有点多,这里重点了解:

  • 组件的创建过程是,先创建父组件,再创建子组件
  • 组件的挂载过程是,先挂载子组件,再挂载父组件。
  • 最终一起渲染到视图。

可以总结出来,组件的粒度不是越小越好。

因为嵌套一层组件,就会重复执行一遍组件的创建过程,比较消耗性能。

组件的抽象过程要合理。

比如页面侧边栏,如果没有其他地方使用,可以组合成一个组件,不需要拆分成多个组件。

知识点小记

  • 全局组件之所以可以在任意组件中使用是因为 Vue 构造函数的选项被合并到了 VueComponent 组件构造函数的选项中
  • 局部组件的使用范围被限制在当前组件内是因为,在创建当前组件的过程中传入的局部组件选项,其它位置无法访问
  • 在 createElement() 函数中调用 createComponent() 创建的是组件的 VNode。组件对象是在组件的 init 钩子函数中创建的,然后在 patch() --> createElm() --> createComponent() 中挂载组件
  • 组件的 data 必须是一个返回对象的函数的原因是:避免组件数据发生变化,影响其他相同的组件
    • Vue 已经做了校验警告处理,预防这种情况。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值