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)
}
...
}
...
}
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!