最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • vue源码分析(六)

    正文概述 掘金(cherish553)   2020-11-27   485

    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)
          }
          ...
        }
        ...
      }
    

    起源地下载网 » vue源码分析(六)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元