Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Vue 源码分析(组件) #15

Open
yangdui opened this issue May 16, 2020 · 0 comments
Open

Vue 源码分析(组件) #15

yangdui opened this issue May 16, 2020 · 0 comments

Comments

@yangdui
Copy link
Owner

yangdui commented May 16, 2020

Vue 源码分析(组件)

组件注册

局部注册

局部注册比较简单。在组件内部使用 components 选择添加注册的组件即可。

new Vue({
  el: '#app',
  components: {
    'component-a': ComponentA,
    'component-b': ComponentB
  }
})

局部注册的组件会在 Vue 构造函数合并选项时,将局部注册的组件添加到 vm.$options.components 上,所以只能本组件中使用。

全局注册

全局注册是通过 Vue.component(tagName, options) 注册的。Vue.component 是在 src/core/global-api/assets.js 文件中定义的

 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') {
          validateComponentName(id)
        }
        if (type === 'component' && isPlainObject(definition)) {
          definition.name = definition.name || id
          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
      }
    }
  })
}

ASSET_TYPES 包括以下选项:

export const ASSET_TYPES = [
  'component',
  'directive',
  'filter'
]
if (type === 'component' && isPlainObject(definition)) {
  definition.name = definition.name || id
  definition = this.options._base.extend(definition)
}

...

this.options[type + 's'][id] = definition
return definition

name 属性就取 name 或者 id 选项。

this.options._base 就是 Vue 构造函数。所以组件就是通过 Vue.extend 创建,最后添加到 Vue.options[components] 上,所以能全局访问。

Vue.extend

上一节我们知道全局组件是通过 Vue.extend 创建的,这里详细介绍这个函数,它是定义在 src/core/global-api/extend.js 文件中

 Vue.extend = function (extendOptions: Object): Function {
    extendOptions = extendOptions || {}
    const Super = this
    const SuperId = Super.cid
    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) {
      this._init(options)
    }
    Sub.prototype = Object.create(Super.prototype)
    Sub.prototype.constructor = Sub
    Sub.cid = cid++
    Sub.options = mergeOptions(
      Super.options,
      extendOptions
    )
    Sub['super'] = Super

    // 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.
    if (Sub.options.props) {
      initProps(Sub)
    }
    if (Sub.options.computed) {
      initComputed(Sub)
    }

    // allow further extension/mixin/plugin usage
    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
    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
    cachedCtors[SuperId] = Sub
    return Sub
  }

Vue.extend 函数总体是通过原型继承,创建 Vue 构造器的子类 Sub。

Vue.extend 函数开始有缓存策略,如果使用两个相同的对象创建 Sub,返回的结果是一样的。

const obj = {
	template: `<div>extend</div>`,
	data: function () {
		return {
			a: 1
		}
	}
};

let res = Vue.extend(obj);
let res1 = Vue.extend(obj);

console.log(res === res1) // true

res 和 res1 都是通过相同 obj 创建的,最后 res 和 res1 是同一个。

 const Sub = function VueComponent (options) {
 	 this._init(options)
 }
 Sub.prototype = Object.create(Super.prototype)
 Sub.prototype.constructor = Sub

这里就是继承了 Vue 构造函数。

接下来就是初始化 Sub, 添加各种属性和方法以及,这些方法和属性就是 Vue 的全局属性和方法。

异步组件

平时我们可能会用到异步组件

Vue.component('async-example', function (resolve, reject) {
  setTimeout(function () {
    // 向 `resolve` 回调传递组件定义
    resolve({
      template: '<div>I am async!</div>'
    })
  }, 1000)
})

Vue.component 注册的不在是一个对象,而是一个函数。所以 Vue.component 函数不会执行 Vue.extend。但是在最后渲染的时候会调用 createComponent 函数,最后调用 resolveAsyncComponent 函数处理异步组件。

createComponent 函数会在组件渲染的时候说明。

resolveAsyncComponent 函数定义在 src/core/vdom/helpersresolve-async-component.js 文件中

export function resolveAsyncComponent (
  factory: Function,
  baseCtor: Class<Component>
): Class<Component> | void {
  if (isTrue(factory.error) && isDef(factory.errorComp)) {
    return factory.errorComp
  }

  if (isDef(factory.resolved)) {
    return factory.resolved
  }

  const owner = currentRenderingInstance
  if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
    // already pending
    factory.owners.push(owner)
  }

  if (isTrue(factory.loading) && isDef(factory.loadingComp)) {
    return factory.loadingComp
  }

  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) => {
      for (let i = 0, l = owners.length; i < l; i++) {
        (owners[i]: any).$forceUpdate()
      }

      if (renderCompleted) {
        owners.length = 0
        if (timerLoading !== null) {
          clearTimeout(timerLoading)
          timerLoading = null
        }
        if (timerTimeout !== null) {
          clearTimeout(timerTimeout)
          timerTimeout = null
        }
      }
    }

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

    const reject = once(reason => {
      process.env.NODE_ENV !== 'production' && warn(
        `Failed to resolve async component: ${String(factory)}` +
        (reason ? `\nReason: ${reason}` : '')
      )
      if (isDef(factory.errorComp)) {
        factory.error = true
        forceRender(true)
      }
    })

    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)) {
          factory.errorComp = ensureCtor(res.error, baseCtor)
        }

        if (isDef(res.loading)) {
          factory.loadingComp = ensureCtor(res.loading, baseCtor)
          if (res.delay === 0) {
            factory.loading = true
          } else {
            timerLoading = setTimeout(() => {
              timerLoading = null
              if (isUndef(factory.resolved) && isUndef(factory.error)) {
                factory.loading = true
                forceRender(false)
              }
            }, res.delay || 200)
          }
        }

        if (isDef(res.timeout)) {
          timerTimeout = setTimeout(() => {
            timerTimeout = null
            if (isUndef(factory.resolved)) {
              reject(
                process.env.NODE_ENV !== 'production'
                  ? `timeout (${res.timeout}ms)`
                  : null
              )
            }
          }, res.timeout)
        }
      }
    }

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

resolveAsyncComponent 函数有两个参数:asyncFactory 就是异步函数,baseCtor 是 Vue 构造函数。根据 Vue 官方文档,异步组件有三种形式:

  • asyncFactory 是

    function (resolve, reject) {
    	...
    }
    
  • asyncFactory 返回一个 Promise

    Vue.component(
      'async-webpack-example',
      // 这个 `import` 函数会返回一个 `Promise` 对象。
      () => import('./my-async-component')
    )
    
  • 有加载状态的异步组件

    const AsyncComponent = () => ({
      // 需要加载的组件 (应该是一个 `Promise` 对象)
      component: import('./MyComponent.vue'),
      // 异步组件加载时使用的组件
      loading: LoadingComponent,
      // 加载失败时使用的组件
      error: ErrorComponent,
      // 展示加载时组件的延时时间。默认值是 200 (毫秒)
      delay: 200,
      // 如果提供了超时时间且组件加载也超时了,
      // 则使用加载失败时使用的组件。默认值是:`Infinity`
      timeout: 3000
    })
    

    其中 component 属性要求返回一个 Promise 对象。

异步组件从加载到最后渲染出来,resolveAsyncComponent 会执行两次。

第一次执行 resolveAsyncComponent 函数,返回值为 undefined 或者 AsyncComponent.loading。只有存在 loading 属性,并且 delay 为 0 的情况才返回 loading 值。如果返回值为 undefined,会通过 createAsyncPlaceholder 函数创建一个注释节点作为占位符。

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

当异步组件获取成功时,会通过 forceRender 函数调用 $forceUpdate 更新组件,在更新组件的过程中,第二次调用 createAsyncPlaceholder 函数,此时 asyncFactory 函数的 error 或者 resolved 存在值。

if (isTrue(factory.error) && isDef(factory.errorComp)) {
	return factory.errorComp
}

if (isDef(factory.resolved)) {
	return factory.resolved
}

会进入其中一个 if 。

在 createAsyncPlaceholder 函数中有 owner 变量。

const owner = currentRenderingInstance
if (owner && isDef(factory.owners) && factory.owners.indexOf(owner) === -1) {
	// already pending
	factory.owners.push(owner)
}

一个异步组件可能有多个组件使用,owner 代表当前组件。如果有两个以上组件使用,会使用 factory.owners 数组将相关组件存起来。

if (owner && !isDef(factory.owners)) {
   const owners = factory.owners = [owner]
   
   ....
   
 }

这段代码避免二次执行异步组件。

createElement 函数的第一个参数也可以是一个 async 函数,最终也会进入 resolveAsyncComponent 函数中。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant