前言
本章项目地址- 通过Vue.component方法 创建一个方法 基于
Object.create(Vue.prototype) - new Vue() 是否存在componets进行合并,并可通过(
__proto__)链式调用 - 生成vnode虚拟节点 并对组件进行特殊处理
data.hook = { init(){} } - 生成真实的dom节点
new Componet().$mount() -> vm.$el
正题
示例
<div id="app">
<my-button></my-button>
</div>
<script>
Vue.component('my-button', {
template: '<button>hello</button>'
})
let vm = new Vue({
components: {
'my-button': {
template: '<button>world</button>',
}
}
})
vm.$mount('#app')
</script>
Vue.component
mergeOptions 合并options方法
/** 存放全局的配置 每个组件初始化的时候 都要与其合并 */
Vue.options = {}
/** 子类可以通过_base, 找到Vue */
Vue.options._base = Vue
Vue.options.components = {}
Vue.component = function (id, definition) {
definition = this.options._base.extend(definition)
this.options.components[id] = definition
}
/** 核心方法 */
/** 给个对象 返回继承Vue的类 */
Vue.extend = function (opts) {
const Super = this
const Sub = function VueComponent(options) {
this._init(options)
}
Sub.prototype = Object.create(Super.prototype)
Sub.prototype.constructor = Sub
// 只与Vue.options 合并 如果全局options有东西进行合并 如Vue.mixin...
Sub.options = mergeOptions(Super.options, opts)
return Sub
}
/** 辅助方法 **/
/**
* @description Vue.componet 组件的hook函数
*/
strats.components = function(parentVal, childVal) {
let options = Object.create(parentVal)
if (childVal) {
for (let key in childVal) {
options[key] = childVal[key]
}
}
return options
}
/** 存放各种策略 如生命周期... */
let strats = {}
/**
* @description 合并Vue.options
*/
export function mergeOptions(parent, child) {
const options = {}
for (let key in parent) {
mergeField(key)
}
for (let key in child) {
if (parent.hasOwnProperty(key)){
continue
}
mergeField(key)
}
function mergeField(key) {
let parentVal = parent[key]
let childVal = child[key]
if (strats[key]) {
options[key] = strats[key](parentVal, childVal)
} else {
if (isObject(parentVal) && isObject(childVal)) {
options[key] = { ...parentVal, ...childVal }
} else {
options[key] = parentVal || childVal
}
}
}
return options
}
生成vnode时创建hook函数,并在vnode上 加new Ctor()组件实例, 在生成真实的dom时 执行此hook函数 返回vnode上的组件实例.$el
- 生成vnode文件
/**
* @description 是否是对象
*/
export function isObject(data) {
return typeof data === 'object' && data !== null
}
/**
* @description 是否是原生标签
*/
export function isReservedTag(str) {
let reservedTag = 'a,div,span,ul,li,p,img,button'
return reservedTag.includes(str)
}
/**
* @description 创建标签的vnode
*/
export function createElement(vm, tag, data = {}, ...children) {
// **看这里** tag是不是组件
if (isReservedTag(tag)) {
return vnode(vm, tag, data, data.key, children, undefined)
} else {
const Ctor = vm.$options.components[tag]
return createComponent(vm, tag, data, data.key, children, Ctor)
}
}
/**
* @description 创建组件的vnode
*/
function createComponent(vm, tag, data, key, children, Ctor) {
// 组件是不是构造函数
if (isObject(Ctor)) {
Ctor = vm.$options._base.extend(Ctor)
}
data.hook = {
init(vnode) {
let vm = vnode.componentInstance = new Ctor({_isComponent: true})
vm.$mount()
}
}
return vnode(vm, `vue-component-${tag}`, data, key, undefined, undefined, {Ctor, children})
}
/**
* @description 创建文本的vnode
*/
export function createTextElement(vm, text) {
return vnode(vm, undefined, undefined, undefined, undefined, text)
}
/** 核心方法 */
/**
* @description 套装vnode
*/
function vnode(vm, tag, data, key, children, text, componentOptions) {
return {
vm,
tag,
data,
key,
children,
text,
componentOptions
// .....
}
}
2.生成真实的dom
export function patch(oldVnode, vnode) {
// 组件没有oldVnode
if (!oldVnode) {
return createElm(vnode)
}
if (oldVnode.nodeType == 1) {
const parentElm = oldVnode.parentNode
let elm = createElm(vnode)
parentElm.insertBefore(elm, oldVnode.nextSibling)
parentElm.removeChild(oldVnode);
return elm
}
}
/**
* @description 创建组件的真实节点
*/
function createComponent(vnode) {
let i = vnode.data
if ((i = i.hook) && (i = i.init)) {
i(vnode)
}
if (vnode.componentInstance) {
return true
}
}
/** 核心方法 */
/**
* @description 创建真实的节点元素 并赋值与vnode上el
*/
function createElm(vnode) {
let { tag, data, children, text, vm } = vnode
if (typeof tag === 'string') {
// 是不是组件
if (createComponent(vnode)) {
return vnode.componentInstance.$el
}
vnode.el = document.createElement(tag)
children.forEach(child => {
vnode.el.appendChild(createElm(child))
})
} else {
vnode.el = document.createTextNode(text)
}
return vnode.el
}