上一篇:Vue2.x 源码 - 生命周期
在 Vue 中,用户在自定义组件之前都必须先注册组件,Vue 提供了两种注册方式:全局注册、局部注册;这两种注册组件的方式结果是不同的,全局注册的组件可以在整个应用中直接使用,局部注册的组件只能在当前组件中使用;接下来就看看这两种注册方式。
全局注册
要注册⼀个全局组件,可以使⽤ Vue.component(tagName, options)
,例如:
Vue.component('name', {
//选项
})
那么, Vue.component
函数是在什么时候定义的呢,它的定义过程发⽣在最开始初始化 Vue 的全局函数的时候,代码在 src/core/global-api/assets.js
中:
1、初始化
Vue.options = Object.create(null)
ASSET_TYPES.forEach(type => {
Vue.options[type + 's'] = Object.create(null)
})
2、API 实现
export const ASSET_TYPES = ['component', 'directive','filter']
export function initAssetRegisters (Vue: GlobalAPI) {
/**
* Create asset registration methods.
*/
ASSET_TYPES.forEach(type => {
Vue[type] = function (
id: string, //名称
definition: Function | Object //方法
): Function | Object | void {
// 未传入 definition,则表示是获取,直接return
if (!definition) {
return this.options[type + 's'][id]
} else {
// 注册组件,检查组件名是否规范
if (process.env.NODE_ENV !== 'production' && type === 'component') {
validateComponentName(id)
}
if (type === 'component' && isPlainObject(definition)) {
//在definition中没有传name,id就是组件的name
definition.name = definition.name || id
//把definition传入extend,返回Vue的子构造器,所以component的第二个参数可以是Vue.extend函数
definition = this.options._base.extend(definition)
}
if (type === 'directive' && typeof definition === 'function') {
//默认 bind和 update都是入参函数
definition = { bind: definition, update: definition }
}
this.options[type + 's'][id] = definition
return definition
}
}
})
}
函数⾸先遍历 ASSET_TYPES ,如果没有传入方法表示获取,传入了则表示是创建;然后对名称合法性进行校验,是 component 的话调用 this.options._base.extend(definition)
在 global-api.js
页面有这样一串代码 Vue.options._base = Vue
所以这里相当于是 Vue.extend
, 这串代码相当于 Vue.extend
把这个对象转换成⼀个继承 于 Vue 的构造函数,最后通过 this.options[type + 's'][id] = definition
把它挂载到 Vue.options.components
上:
import Vue from 'vue'
import HelloWorld from '@/components/HelloWorld.vue'
// 注册前
const options = {
components: {}
}
// 注册
Vue.component('HelloWorld', HelloWorld)
// 注册后
const options = {
components: {
HelloWorld: function VueComponent () { ... }
}
}
在 extend 继承的时候会有这样一段代码:
Sub.options = mergeOptions(
Super.options,
extendOptions
)
也就是说它会把 Vue.options 合并到 Sub.options,也就是组件的 optinons 上;然后在组件的实例化阶段,会执⾏ merge options
逻辑,把 Sub.options.components
合并到 vm.$options.components
上;
所以全局注册组件 都在Vue.options.components
选项上,这就是全局注册的组件可以在任意地方使用的根本原因了;
在合并参数的时候有下面这段代码:
function mergeAssets (
parentVal: ?Object,
childVal: ?Object,
vm?: Component,
key: string
): Object {
const res = Object.create(parentVal || null)
if (childVal) {
process.env.NODE_ENV !== 'production' && assertObjectType(key, childVal, vm)
return extend(res, childVal)
} else {
return res
}
}
将全局注册组件合并到子组件的 components 选项的原型上;
然后在创建 vnode 的过程中,会执行 _createElement
方法,在 src/core/vdom/create-element.js
中,涉及到组件的时候有这样一个判断 isDef(Ctor = resolveAsset(context.$options, 'components', tag)
;先看⼀下 resolveAsset
的定义,在 src/core/utils/options.js
中:
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
}
通过多种方法来获取组件的 id,也可以说是组件名;目的是拿到这个组件的构造函数并作为 createComponent
方法的参数来生成组件的 vnode;
全局组件注册渲染流程:
1、创建 components :在 globalApi 中进行初始化,赋值
Object.create(null)
;
2、globalApi 的initAssetRegisters
方方法中extend 继承 :创建了一个基于父Vue的子类,继承父类的方法;
3、extend 中 mergeOptions 参数合并 :并合并父类的属性;
4、在挂载过程中先后调用:mountComponent 、render、_update,生成 updateComponent 方法,然后用 updateComponent 创建 Watcher;
5、在 render() 过程中执行 createElement,然后执行 createComponent 来生成组件的 vnode;
6、createComponent 过程中回执行 installComponentHooks 来出来组件的钩子函数;
7、获取到的 render 函数更新渲染;
局部注册
import msg from './components/msg'
export default {
components:{ msg }
}
局部注册组件会在 Vue 实例初始化阶段在合并选项的时候把 components 合并搭配 vm.$options.components
上,这样在resolveAsset
的时候就可以拿到组件的构造函数并最为参数传递给 createComponent
方法来生成组件的 vnode;
局部组件比较简单,就是在初始化的时候作为参数合并到
vm.$options.components
上
createComponent
的流程可以参考:Vue2.x 源码 - render 函数生成 VNode
组件化和模块化区别
1、组件化:从 UI 界面上来划分,包含这个组件的 HTML、CSS、JS,可复用;
2、模块化:从代码逻辑层面来划分,包含一些复用的 JS、CSS 等;
下一篇:Vue2.x 源码 - 异步组件