Appearance
component
javascript
// 注册全局组件,挂载在 Vue.options.components
Vue.component({ // Vue.options.components
template: '<div>hello world</div>'
})
// 注册局部组件,挂载在 vm.$options.components
new Vue({
components: { // vm.$options.components
'my-component': {
template: '<div>hello world</div>'
}
}
})javascript
// 对于全局组件、全局指令、全局过滤器来说,优先级都是局部 > 全局,
// vm.$options 中没有才会去 Vue.options 中找,因此对于
// components、directives、filters 使用同一个合并策略 mergeAssets。
// 以 components 属性为例,当合并配置项时发现属性是 components 时触发 mergeAssets
// 合并策略。内部以 Vue.components 对象的原型创建一个空对象 options,这个空对象用来
// 保存局部组件,之后如果遍历 vm.$options.components 每个属性并保存到 options 中,
// 最后返回 options。当使用组件时,先在自身找,没找到才沿着原型链去找全局的。
const ASSET_TYPES = [
'component',
'directive',
'filter'
]
ASSET_TYPES.forEach(function (type) {
strats[type + 's'] = mergeAssets
});
function mergeAssets (parentVal, childVal) {
const options = Object.create(parentVal || null) // 自己没有时就去原型链上找
if (childVal) {
for (let key in childVal) {
options[key] = childVal[key] // 局部组件优先
}
}
return options
}javascript
Vue.component = function (id, definition) {
definition = this.options._base.extend(definition) // Vue.extend
}javascript
// 在 render => vnode 时通过 isReservedTag(tag) 检测标签是否属于原生标签来判断是否
// 是组件,如果不是原生标签那么通过 vm.$options.components[tag] 获取Ctor,最后通过
// createComponent 生成组件 vnode。
function createElement (context, tag, data, children) {
// 不是原生标签生成组件 vnode
let Ctor = context.$options.components[tag] // 配置项
return createComponent(Ctor, data || {}, context, children, tag)
}javascript
// Ctor 可能是配置项,即一个对象,也可能是继承了 Vue 的子类,即即 VueComonent
// 构造函数,取决于我们有没有调用 Vue.extend。
// createComponent 内部先判断 Ctor 是不是对象,是对象就手动调用 Vue.extend(Ctor)
// 生成子类,即 VueComonent 构造函数,之后会在 vnode 的 data 属性中添加一个 hook
// 对象,对象内部定义一个 init 方法,init 方法会 new Ctor 生成组件实例,再手动调用
// vc.$mount 渲染成 DOM,因为组件没有 $el,_init 时不会自动挂载,最后返回 vnode。
createComponentfunction createComponent () {
if (isObject(Ctor)) { // 没有使用Vue.extend时则手动调用
Ctor = context.$options._base.extend(Ctor) // 创建VueComponent, 即Sub
}
data.hook = {
init (vnode) { // 初始化init钩子函数
// new Sub, 生成vc
let vc = vnode.componentInstance = new Ctor({ _isComponent: true })
vc.$mount() // 手动挂载, 因为没有$el, _init时不会自动挂载
}
}
return VNode(
context,
`vue-component-${tag}`, // tag
data,
undefined,
undefined,
undefined,
undefined,
{ Ctor, children} // componentOptions
)
}javascript
// 在 patch 时,因为组件没有 $el,所以 oldVnode 未定义,说明是初始化渲染组件
// 调用 createElm 生成 DOM
function patch (oldVnode, vnode) {
if (isUndef(oldVnode)) { // 组件没有$el
return createElm(vnode)
}
}
// 渲染原生标签和渲染组件标签都是走 createElm 方法,createElm 中会先尝试调用
// createComponent(vnode) 尝试渲染组件,如果返回值为真直接 return,否则接下来就执行
// 渲染原生标签的逻辑
function createElm (vnode) {
let { tag, data, children, text, context } = vnode
if (typeof tag === 'string') {
// 尝试找vnode.data.hook.init并调用, 渲染成组件对应的真实DOM并返回true
// 否则不是组件, 渲染成元素节点
if (createComponent(vnode)) {
return vnode.componentInstance.$el
}
// 渲染成元素节点
vnode.elm = document.createElement(tag)
updateProps({}, vnode) // 初始化时更新样式、属性
if (children) {
for (let i = 0, l = children.length; i < l; i++) {
vnode.elm.appendChild(createElm(children[i]))
}
}
} else {
// 渲染成文本节点
vnode.elm = document.createTextNode(text)
}
return vnode.elm
}
// createComponent 内部直接看能不能从 vnode 中找到 vnode.data.hook.init,
// 如果找得到直接把init方法调用,最后返回 true。
// 调用vnode.data.hook.init => new Ctor => _init => HTML => render => AST
// => VNode => DOM
function createComponent(vnode) {
let i = vnode.data
if (i) {
if ((i = i.hook) && (i = i.init)) {
i(vnode) // vnode.data.hook.init
return true
}
}
}