• 组件化可以让我们方便的把页面拆分成多个可重用的组件
  • 组件是独立的,系统内可重用,组件之间可以嵌套
  • 有了组件可以像搭积木一样开发网页
  • 下面我们将从源码的角度来分析 Vue 组件内部如何工作
    • 组件实例的创建过程是从上而下
    • 组件实例的挂载过程是从下而上

组件声明

全局组件的定义方式:

  1. Vue.component('comp', {
  2. template: '<h1>hello</h1>'
  3. })
  • Vue.component() 入口

    • 创建组件的构造函数,挂载到 Vue 实例的 vm.options.component.componentName = Ctor
      1. // src\core\global-api\index.js
      2. // 注册 Vue.directive()、 Vue.component()、Vue.filter()
      3. initAssetRegisters(Vue)
      4. // src\core\global-api\assets.js
      5. if (type === 'component' && isPlainObject(definition)) {
      6. definition.name = definition.name || id
      7. definition = this.options._base.extend(definition)
      8. }
      9. ……
      10. // 全局注册,存储资源并赋值
      11. // this.options['components']['comp'] = Ctor
      12. this.options[type + 's'][id] = definition
      13. // src\core\global-api\index.js
      14. // this is used to identify the "base" constructor to extend all plainobject
      15. // components with in Weex's multi-instance scenarios.
      16. Vue.options._base = Vue
      17. // src\core\global-api\extend.js
      18. Vue.extend()
  • 组件构造函数的创建

    1. const Sub = function VueComponent(options) {
    2. this._init(options)
    3. }
    4. Sub.prototype = Object.create(Super.prototype)
    5. Sub.prototype.constructor = Sub
    6. Sub.cid = cid++
    7. Sub.options = mergeOptions(
    8. Super.options,
    9. extendOptions
    10. )
    11. Sub['super'] = Super
    12. // For props and computed properties, we define the proxy getters on
    13. // the Vue instances at extension time, on the extended prototype. This
    14. // avoids Object.defineProperty calls for each instance created.
    15. if (Sub.options.props) {
    16. initProps(Sub)
    17. }
    18. if (Sub.options.computed) {
    19. initComputed(Sub)
    20. }
    21. // allow further extension/mixin/plugin usage
    22. Sub.extend = Super.extend
    23. Sub.mixin = Super.mixin
    24. Sub.use = Super.use
    25. // create asset registers, so extended classes
    26. // can have their private assets too.
    27. ASSET_TYPES.forEach(function (type) {
    28. Sub[type] = Super[type]
    29. })
    30. // enable recursive self-lookup
    31. if (name) {
    32. Sub.options.components[name] = Sub
    33. }

组件 VNode 的创建

  • 创建根组件,首次 _render() 时,会得到整棵树的 VNode 结构
  • 整体流程:new Vue() —> $mount() —> vm._render() —> createElement() —> createComponent()
  • 创建组件的 VNode,初始化组件的 hook 钩子函数
  1. // 1. _createElement() 中调用 createComponent()
  2. // src\core\vdom\create-element.js
  3. else if ((!data || !data.pre) &&
  4. isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
  5. // 查找自定义组件构造函数的声明
  6. // 根据 Ctor 创建组件的 VNode
  7. // component
  8. vnode = createComponent(Ctor, data, context, children, tag)
  9. // 2. createComponent() 中调用创建自定义组件对应的 VNode
  10. // src\core\vdom\create-component.js
  11. export function createComponent(
  12. Ctor: Class<Component> | Function | Object | void,
  13. data: ?VNodeData,
  14. context: Component,
  15. children: ?Array<VNode>,
  16. tag?: string
  17. ): VNode | Array<VNode> | void {
  18. if (isUndef(Ctor)) {
  19. return
  20. }
  21. ……
  22. // install component management hooks onto the placeholder node
  23. // 安装组件的钩子函数 init/prepatch/insert/destroy
  24. // 初始化了组件的 data.hooks 中的钩子函数
  25. installComponentHooks(data)
  26. // return a placeholder vnode
  27. const name = Ctor.options.name || tag
  28. // 创建自定义组件的 VNode,设置自定义组件的名字
  29. // 记录this.componentOptions = componentOptions
  30. const vnode = new VNode(
  31. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  32. data, undefined, undefined, undefined, context,
  33. { Ctor, propsData, listeners, tag, children },
  34. asyncFactory
  35. )
  36. return vnode
  37. }
  38. // 3. installComponentHooks() 初始化组件的 data.hook
  39. function installComponentHooks(data: VNodeData) {
  40. const hooks = data.hook || (data.hook = {})
  41. // 用户可以传递自定义钩子函数
  42. // 把用户传入的自定义钩子函数和 componentVNodeHooks 中预定义的钩子函数合并
  43. for (let i = 0; i < hooksToMerge.length; i++) {
  44. const key = hooksToMerge[i]
  45. const existing = hooks[key]
  46. const toMerge = componentVNodeHooks[key]
  47. if (existing !== toMerge && !(existing && existing._merged)) {
  48. hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
  49. }
  50. }
  51. }
  52. // 4. 钩子函数定义的位置(init()钩子中创建组件的实例)
  53. // inline hooks to be invoked on component VNodes during patch
  54. const componentVNodeHooks = {
  55. init(vnode: VNodeWithData, hydrating: boolean): ?boolean {
  56. if (
  57. vnode.componentInstance &&
  58. !vnode.componentInstance._isDestroyed &&
  59. vnode.data.keepAlive
  60. ) {
  61. // kept-alive components, treat as a patch
  62. const mountedNode: any = vnode // work around flow
  63. componentVNodeHooks.prepatch(mountedNode, mountedNode)
  64. } else {
  65. // 创建组件实例挂载到 vnode.componentInstance
  66. const child = vnode.componentInstance =
  67. createComponentInstanceForVnode(
  68. vnode,
  69. activeInstance
  70. )
  71. // 调用组件对象的 $mount(),把组件挂载到页面
  72. child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  73. }
  74. },
  75. prepatch(oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
  76. ……
  77. },
  78. insert(vnode: MountedComponentVNode) {
  79. ……
  80. },
  81. destroy(vnode: MountedComponentVNode) {
  82. ……
  83. }
  84. }
  85. //5 .创建组件实例的位置,由自定义组件的 init() 钩子方法调用
  86. export function createComponentInstanceForVnode(
  87. vnode: any, // we know it's MountedComponentVNode but flow doesn't
  88. parent: any, // activeInstance in lifecycle state
  89. ): Component {
  90. const options: InternalComponentOptions = {
  91. _isComponent: true,
  92. _parentVnode: vnode,
  93. parent
  94. }
  95. // check inline-template render functions
  96. const inlineTemplate = vnode.data.inlineTemplate
  97. if (isDef(inlineTemplate)) {
  98. options.render = inlineTemplate.render
  99. options.staticRenderFns = inlineTemplate.staticRenderFns
  100. }
  101. // 创建组件实例
  102. return new vnode.componentOptions.Ctor(options)
  103. }

组件实例的创建和挂载过程

  • Vue._update() —> patch() —> createElm() —> createComponent()
  1. // src\core\vdom\patch.js
  2. // 1. 创建组件实例,挂载到真实 DOM
  3. function createComponent(vnode, insertedVnodeQueue, parentElm, refElm) {
  4. let i = vnode.data
  5. if (isDef(i)) {
  6. const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
  7. if (isDef(i = i.hook) && isDef(i = i.init)) {
  8. // 调用 init() 方法,创建和挂载组件实例
  9. // init() 的过程中创建好了组件的真实 DOM,挂载到了 vnode.elm 上
  10. i(vnode, false /* hydrating */)
  11. }
  12. // after calling the init hook, if the vnode is a child component
  13. // it should've created a child instance and mounted it. the child
  14. // component also has set the placeholder vnode's elm.
  15. // in that case we can just return the element and be done.
  16. if (isDef(vnode.componentInstance)) {
  17. // 调用钩子函数(VNode的钩子函数初始化属性/事件/样式等,组件的钩子函数)
  18. initComponent(vnode, insertedVnodeQueue)
  19. // 把组件对应的 DOM 插入到父元素中
  20. insert(parentElm, vnode.elm, refElm)
  21. if (isTrue(isReactivated)) {
  22. reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
  23. }
  24. return true
  25. }
  26. }
  27. }
  28. // 2. 调用钩子函数,设置局部作用于样式
  29. function initComponent(vnode, insertedVnodeQueue) {
  30. if (isDef(vnode.data.pendingInsert)) {
  31. insertedVnodeQueue.push.apply(insertedVnodeQueue,
  32. vnode.data.pendingInsert)
  33. vnode.data.pendingInsert = null
  34. }
  35. vnode.elm = vnode.componentInstance.$el
  36. if (isPatchable(vnode)) {
  37. // 调用钩子函数
  38. invokeCreateHooks(vnode, insertedVnodeQueue)
  39. // 设置局部作用于样式
  40. setScope(vnode)
  41. } else {
  42. // empty component root.
  43. // skip all element-related modules except for ref (#3455)
  44. registerRef(vnode)
  45. // make sure to invoke the insert hook
  46. insertedVnodeQueue.push(vnode)
  47. }
  48. }
  49. // 3. 调用钩子函数
  50. function invokeCreateHooks(vnode, insertedVnodeQueue) {
  51. // 调用 VNode 的钩子函数,初始化属性/样式/事件等
  52. for (let i = 0; i < cbs.create.length; ++i) {
  53. cbs.create[i](emptyNode, vnode)
  54. }
  55. i = vnode.data.hook // Reuse variable
  56. // 调用组件的钩子函数
  57. if (isDef(i)) {
  58. if (isDef(i.create)) i.create(emptyNode, vnode)
  59. if (isDef(i.insert)) insertedVnodeQueue.push(vnode)
  60. }
  61. }