vue源码分析(六)

645 阅读2分钟

5.组件注册

组件注册分为两种方式,全局组件和局部组件

5-1全局组件

全局组件的注册方式为Vue.component(xxx,xxx),他注册在initGlobalAPI函数中,通过调用initAssetRegisters函数,ASSET_TYPES中有component

// src/shared/constants.js
export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]

initAssetRegisters函数主要定义了他的name,之后通过Vue.extend方法把传入的object作为一个组件构造函数在全局option进行注册

// src/core/global-api/assets.js
 export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string, 
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
       ...
      } else {
        ...
        if (type === 'component' && isPlainObject(definition)) {
          // 如果组件有name,那么取他的name作为name,否则id
          definition.name = definition.name || id
          // 创造组件的构造函数 this.options._base 是Vue
          definition = this.options._base.extend(definition)
        }
        ...
        // 把组件构造器赋值给全局的options
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

在创建vnode的过程中_createElement方法会判断传入的tag是否是一个string,如果是string那么会判断是否是保留标签,如果不是,会调用Ctor = resolveAsset(context.$options, 'components', tag)

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  ...
  if (typeof tag === 'string') {
    // 如果是原生保留标签
    if (config.isReservedTag(tag)) {
     ...
    } else if ((!data || !data.pre) && isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
     ...
    }
  } else {
    // direct component options / constructor
    vnode = createComponent(tag, data, context, children)
  }
  ...
}

resolveAsset函数会根据传入的options也就是this.$options去找对应的是否有全局注册相关组件,其中也通过大小写,下划线等等转换,尝试去寻找,如果找到的话返回他(组件的构造函数),如果找到对应的组件,那么Ctor就是当前组件的构造函数通过 vnode = createComponent(Ctor, data, context, children, tag)创建组件的vnode

// src/core/util/options.js
/**
 * Resolve an asset.
 * This function is used because child instances need access
 * to assets defined in its ancestor chain.
 */
export function resolveAsset (
  options: Object,
  type: string,
  id: string,
  warnMissing?: boolean 
): any {
  /* istanbul ignore if */
  if (typeof id !== 'string') {
    return
  }
  const assets = options[type]
  // check local registration variations first
  if (hasOwn(assets, id)) return assets[id]
  const camelizedId = camelize(id)
  if (hasOwn(assets, camelizedId)) return assets[camelizedId]
  const PascalCaseId = capitalize(camelizedId)
  if (hasOwn(assets, PascalCaseId)) return assets[PascalCaseId]
  // fallback to prototype chain
  const res = assets[id] || assets[camelizedId] || assets[PascalCaseId]
  if (process.env.NODE_ENV !== 'production' && warnMissing && !res) {
    warn(
      'Failed to resolve ' + type.slice(0, -1) + ': ' + id,
      options
    )
  }
  return res
}

5-2局部组件

局部注册会通过Vue.extend中的mergeOptions(Super.options,extendOptions)函数进行配置的合并,其中extendOptions是组件传入的内容。

 Vue.extend = function (extendOptions: Object): Function {
    ...
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    ...
}

在组件初始化的时候会调用initInternalComponent,在const opts = vm.$options = Object.create(vm.constructor.options)的时候赋值给组件的options,局部注册是把组件扩展到了sub.options上,所以在别的组件中是无法访问的

6.异步组件

6-1工厂函数

Vue.component('HelloWorld', function(reslove) {
  require(['./components/HelloWorld'], function(res) {
    reslove(res)
  })
})

异步组件的注册在initAssetRegisters中,会直接把definition赋值给options

// src/core/global-api/assets.js
export function initAssetRegisters (Vue: GlobalAPI) {
  /**
   * Create asset registration methods.
   */
  ASSET_TYPES.forEach(type => {
    Vue[type] = function (
      id: string, 
      definition: Function | Object
    ): Function | Object | void {
      if (!definition) {
       ...
      } else {
        /* istanbul ignore if */
        if (process.env.NODE_ENV !== 'production' && type === 'component') {
          ...
        }
        if (type === 'component' && isPlainObject(definition)) {
         ...
        }
        if (type === 'directive' && typeof definition === 'function') {
         ...
        }
        this.options[type + 's'][id] = definition
        return definition
      }
    }
  })
}

在调用createComponent的时候,如果是一个异步组件。那么会先调用resolveAsyncComponent

export function createComponent (
  Ctor: Class<Component> | Function | Object | void,
  data: ?VNodeData,
  context: Component,
  children: ?Array<VNode>,
  tag?: string
): VNode | Array<VNode> | void {
  ...
  // async component
  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
      )
    }
  }
 }

resolveAsyncComponent函数中,owner是当前的vm实例,定义局部变量sync为true

// src/core/vdom/helpers/resolve-async-component.js
export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
): Class<Component> | void {
  ...
  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }

 ...

  if (owner && !isDef(factory.owners)) {
    const owners = factory.owners = [owner]
    let sync = true
    let timerLoading = null
    let timerTimeout = null

    ;(owner: any).$on('hook:destroyed', () => remove(owners, owner))

    const forceRender = (renderCompleted: boolean) => {...}

    const resolve = once((res: Object | Class<Component>) => {...})

    const reject = once(reason => {...})

    const res = factory(resolve, reject)

    if (isObject(res)) {
      if (isPromise(res)) {
        // () => Promise
        if (isUndef(factory.resolved)) {
          res.then(resolve, reject)
        }
      } else if (isPromise(res.component)) {
        res.component.then(resolve, reject)

        if (isDef(res.error)) {...}

        if (isDef(res.loading)) {...}

        if (isDef(res.timeout)) {...}
      }
    }

    sync = false
    // return in case resolved synchronously
    return factory.loading
      ? factory.loadingComp
      : factory.resolved
  }
}

resolve函数首先经过了once的一层封装函数通过闭包的方式定义了变量called当首次执行的时候called是false,执行之后called变为了true,则可以保证函数只执行一次

// src/shared/util.js
/**
 * Ensure a function is called only once.
 */
export function once (fn: Function): Function {
  let called = false
  return function () {
    if (!called) {
      called = true
      fn.apply(this, arguments)
    }
  }
}

执行之后Ctor为undefined,满足if (Ctor === undefined),之后执行createAsyncPlaceholder( asyncFactory, data, context, children, tag )创建一个注释节点vnode,加载成功之后会去调用reslove(res)

resolve函数中ensureCtor会帮助我们去处理其他的引入方式,最终返回一个构造器。之后会执行forceRender(true)

   const resolve = once((res: Object | Class<Component>) => {
      // cache resolved
      factory.resolved = ensureCtor(res, baseCtor)
      // invoke callbacks only if this is not a synchronous resolve
      // (async resolves are shimmed as synchronous during SSR)
      if (!sync) {
        forceRender(true)
      } else {
        owners.length = 0
      }
    })

forceRender函数实际上会执行$forceUpdate方法,$forceUpdate方法会调用渲染watcher的update方法,通知页面进行重新渲染,之后会重新调用vm._render,再次执行到createcomponent,这次会拿到异步组件的构造器,完成异步加载组件渲染

// src/core/instance/lifecycle.js
  Vue.prototype.$forceUpdate = function () {
    const vm: Component = this
    if (vm._watcher) {
      vm._watcher.update()
    }
  }

6-2 promise

Vue.compoennt('HelloWorld',()=>import('./component/HelloWorld'))

import会返回一个promise,之前的逻辑都是相同的,在resolveAsyncComponent函数中,res是一个promise,会通过,res.then把异步加载的内容传入,之后的处理也是相同的

export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
): Class<Component> | void {
  ...
  const res = factory(resolve, reject)
  ...
  if (isObject(res)) {
    if (isPromise(res)) {
      // () => Promise
      if (isUndef(factory.resolved)) {
        res.then(resolve, reject)
      }
      ...
    }
    ...
  }