[关闭]
@frank-shaw 2019-11-30T10:15:36.000000Z 字数 5948 阅读 1101

vue源码阅读(七):组件化机制的实现

vue 源码 组件化机制


MVVM框架中,组件化是一种必要性的存在。

通过组件化,将页面做切割,并将其对应的逻辑做一定的抽象,封装成独立的模块。

那么Vue中的组件化是怎样做的呢?我们分几个点来具体阐述这个过程:组件声明过程Vue.component()的具体实现,组件的创建与挂载过程。

组件声明

Vue.component()的声明是一个全局API,我们在/core/index.js中查看函数initGlobalAPI()的代码:

  1. import { initUse } from './use'
  2. import { initMixin } from './mixin'
  3. import { initExtend } from './extend'
  4. import { initAssetRegisters } from './assets'
  5. export function initGlobalAPI (Vue: GlobalAPI) {
  6. //...省略
  7. initUse(Vue)
  8. initMixin(Vue)
  9. initExtend(Vue)
  10. initAssetRegisters(Vue)
  11. }

发现Vue.component()的声明没有明确写在代码里,其声明的过程是动态的。具体过程在initAssetRegisters()中:

  1. import { ASSET_TYPES } from 'shared/constants'
  2. import { isPlainObject, validateComponentName } from '../util/index'
  3. export function initAssetRegisters (Vue: GlobalAPI) {
  4. // ASSET_TYPES : ['component','directive','filter']
  5. ASSET_TYPES.forEach(type => {
  6. Vue[type] = function (
  7. id: string,
  8. definition: Function | Object
  9. ): Function | Object | void {
  10. if (!definition) {
  11. return this.options[type + 's'][id]
  12. } else {
  13. /* istanbul ignore if */
  14. if (process.env.NODE_ENV !== 'production' && type === 'component') {
  15. validateComponentName(id)
  16. }
  17. //component的特殊处理
  18. if (type === 'component' && isPlainObject(definition)) {
  19. //指定name
  20. definition.name = definition.name || id
  21. //转换组件配置对象为构造函数
  22. definition = this.options._base.extend(definition)
  23. }
  24. if (type === 'directive' && typeof definition === 'function') {
  25. definition = { bind: definition, update: definition }
  26. }
  27. //全局注册:options['components'][id] = Ctor
  28. //此处注册之后,就可以在全局其他地方使用
  29. this.options[type + 's'][id] = definition
  30. return definition
  31. }
  32. }
  33. })
  34. }

以一个例子来说明代码的过程吧:

  1. // 定义一个名为 button-counter 的新组件
  2. Vue.component('button-counter', {
  3. data: function () {
  4. return {
  5. count: 0
  6. }
  7. },
  8. template: '<button v-on:click="count++">You clicked me {{ count }} times.</button>'
  9. })

组件的声明过程是:先获取组建的名称,然后将{data:"...", template: "...}转变为构造函数,最后,将该组件的构造函数注册到 this.options.components中。

当其他组件使用到该组件的时候,首先会在this.options.components中查询是否存在该组件,由此实现了全局注册。

自定义组件的创建与挂载

那么,我们想问:这个自定义组件,是在什么时候创建的?

render过程

首先创建的是根组件,首次_render()时,会得到整棵树的VNode结构,其中必然包括了子组件的创建过程。那么子组件的创建会是在哪一步呢?让我们来回顾根组件的创建过程:

  1. new Vue() => $mount() => vm._render() => _createElement() => createComponent()

_render()的过程中,会触发_createElement()。在该函数内部,会对是否是自定义组件进行查询,如果是自定义组件,那么会触发createComponent(),其过程为:

  1. export function _createElement (
  2. context: Component,
  3. tag?: string | Class<Component> | Function | Object,
  4. data?: VNodeData,
  5. children?: any,
  6. normalizationType?: number
  7. ): VNode | Array<VNode> {
  8. ...//省略
  9. // 核心:vnode的生成过程
  10. // 传入tag可能是原生的HTML标签,也可能是用户自定义标签
  11. let vnode, ns
  12. if (typeof tag === 'string') {
  13. let Ctor
  14. ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
  15. // 是原生保留标签,直接创建VNode
  16. if (config.isReservedTag(tag)) {
  17. vnode = new VNode(
  18. config.parsePlatformTagName(tag), data, children,
  19. undefined, undefined, context
  20. )
  21. }else if((!data||!data.pre)&&isDef(Ctor=resolveAsset(context.$options, 'components',tag))) {
  22. // 查看this.options.components中是否存在对应的tag的构造函数声明
  23. // 若存在,需要先创建组件,再创建VNode
  24. vnode = createComponent(Ctor, data, context, children, tag)
  25. } else {
  26. //
  27. vnode = new VNode(
  28. tag, data, children,
  29. undefined, undefined, context
  30. )
  31. }
  32. } else {
  33. // direct component options / constructor
  34. vnode = createComponent(tag, data, context, children)
  35. }
  36. ...//省略
  37. }

那么我们来看createComponent(),它的主要工作是根据构造函数Ctor获取到VNode:

  1. export function createComponent (
  2. Ctor: Class<Component> | Function | Object | void,
  3. data: ?VNodeData,
  4. context: Component,
  5. children: ?Array<VNode>,
  6. tag?: string
  7. ): VNode | Array<VNode> | void {
  8. // 省略...
  9. // 安装组件的管理钩子到该节点上
  10. // 这些管理钩子,会在根组件首次patch的时候调用
  11. installComponentHooks(data)
  12. // 返回VNode
  13. const name = Ctor.options.name || tag
  14. const vnode = new VNode(
  15. `vue-component-${Ctor.cid}${name ? `-${name}` : ''}`,
  16. data, undefined, undefined, undefined, context,
  17. { Ctor, propsData, listeners, tag, children },
  18. asyncFactory
  19. )
  20. // 省略...
  21. return vnode
  22. }

installComponentHooks()具体做了什么事情呢?所谓的组件的管理钩子,到底是些啥东西啊?我们来具体看看:

  1. const componentVNodeHooks = {
  2. // 初始化钩子:创建组件实例、执行挂载
  3. init (vnode: VNodeWithData, hydrating: boolean): ?boolean {
  4. if (
  5. vnode.componentInstance &&
  6. !vnode.componentInstance._isDestroyed &&
  7. vnode.data.keepAlive
  8. ) {
  9. const mountedNode: any = vnode // work around flow
  10. componentVNodeHooks.prepatch(mountedNode, mountedNode)
  11. } else {
  12. //创建自定义组件VueComponent实例,并和对应VNode相互关联
  13. const child = vnode.componentInstance = createComponentInstanceForVnode(
  14. vnode,
  15. activeInstance
  16. )
  17. //创建之后,立刻执行挂载
  18. child.$mount(hydrating ? vnode.elm : undefined, hydrating)
  19. }
  20. },
  21. prepatch (oldVnode: MountedComponentVNode, vnode: MountedComponentVNode) {
  22. ...
  23. },
  24. insert (vnode: MountedComponentVNode) {
  25. ...
  26. },
  27. destroy (vnode: MountedComponentVNode) {
  28. ...
  29. }
  30. }
  31. const hooksToMerge = Object.keys(componentVNodeHooks)
  32. //将自定义组件相关的hooks都放在生成的这个VNode的data.hook中
  33. //在将来的根组件首次patch过程中,自定义组件通过这些hooks完成创建,并立即挂载
  34. function installComponentHooks (data: VNodeData) {
  35. const hooks = data.hook || (data.hook = {})
  36. for (let i = 0; i < hooksToMerge.length; i++) {
  37. const key = hooksToMerge[i]
  38. const existing = hooks[key]
  39. const toMerge = componentVNodeHooks[key]
  40. if (existing !== toMerge && !(existing && existing._merged)) {
  41. hooks[key] = existing ? mergeHook(toMerge, existing) : toMerge
  42. }
  43. }
  44. }

看代码可知,管理钩子一共有四个:init(),prepatch(),insert(),destroy()。看init()的过程:创建自定义组件VueComponent实例,并和对应VNode相互关联,然后立刻执行$mount()方法。

update过程

installComponentHooks()过程中,其实只是将这四个管理钩子放在生成的这个VNode的data.hook中存放。对应的管理钩子调用,在首次执行update()时候,执行patch()的过程里。详细的过程在/core/instance/vdom/patch.jscreateElm()中:

  1. function createElm (
  2. vnode,
  3. insertedVnodeQueue,
  4. parentElm,
  5. refElm,
  6. nested,
  7. ownerArray,
  8. index
  9. ) {
  10. //省略...
  11. //子组件的生成过程
  12. if (createComponent(vnode, insertedVnodeQueue, parentElm, refElm)) {
  13. return
  14. }
  15. //原生标签的生成过程
  16. //省略...
  17. }
  18. function createComponent (vnode, insertedVnodeQueue, parentElm, refElm) {
  19. //获取data
  20. let i = vnode.data
  21. if (isDef(i)) {
  22. const isReactivated = isDef(vnode.componentInstance) && i.keepAlive
  23. if (isDef(i = i.hook) && isDef(i = i.init)) {
  24. //i就是vnode.data.hook.init()方法,在此处创建自定义组件实例,并完成挂载工作
  25. //详细见'./patch.js componentVNodeHooks()'
  26. i(vnode, false /* hydrating */)
  27. }
  28. // after calling the init hook, if the vnode is a child component
  29. // it should've created a child instance and mounted it. the child
  30. // component also has set the placeholder vnode's elm.
  31. // in that case we can just return the element and be done.
  32. //判断自定义组件是否实例化完成
  33. if (isDef(vnode.componentInstance)) {
  34. //自定义组件实例化后,需要完善属性、样式等
  35. initComponent(vnode, insertedVnodeQueue)
  36. //插入到父元素的旁边
  37. insert(parentElm, vnode.elm, refElm)
  38. if (isTrue(isReactivated)) {
  39. reactivateComponent(vnode, insertedVnodeQueue, parentElm, refElm)
  40. }
  41. return true
  42. }
  43. }
  44. }

可以看到,自定义组件的创建过程在patch.jscreateComponent()方法中。通过调用上面提到的vnode.data.hook.init()方法(和文章的上一段联系起来),将自定义组件在此处创建,并且立即调用$mount()

这个时候就可以理解以下结论:

组件创建顺序自上而下
组件挂载顺序自下而上

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注