元件的擴充套件
在 Vue
中有 extend
方法可以擴充套件 Vue
的例項,在上一步中,有一些實現是必須要通過子父元件才能實現,而子元件相當於一個特殊的 Vue
例項,所以這步,我們先把這個擴充套件例項的方法實現。
我們先來看看官網對於 extend
方法的介紹:
使用基礎 Vue 構造器,建立一個“子類”。引數是一個包含元件選項的物件。
從後面一句和具體的使用方法可以得出其實是我們建立例項時,對於傳入引數的擴充套件。對於這個參入引數我們就叫它 options
。
我們接著往下想,既然這個 options
是能擴充套件的,那麼原 Vue
類下,肯定儲存著一個預設 options
,而建立 Vue
例項時,會把傳入的 options
和預設的 options
進行合併。
所以 extend
方法,是對預設 options
進行擴充套件,從而實現擴充套件。
mergeOptions
ok 有了思路,我們來實現它:
首先是預設的 options
,同時我們假設一個方法(mergeOptions
)用來合併 options
let uid = 0
export class Vue extends Event {
···
_init(options) {
let vm = this
// 為了方便引用合併的 options 我們把它掛載在 Vue 例項下
vm.$options = mergeOptions(
this.constructor.options,
options,
vm
)
···
}
}
// 預設的 options
Vue.options = {
// 元件列表
components: {},
// 基類
_base: Vue
}
複製程式碼
接著我們來實現 mergeOptions
:
import R from 'ramda'
export function mergeOptions(parent, child) {
// data/methods/watch/computed
let options = {}
// 合併 data 同名覆蓋
options.data = mergeData(parent.data, child.data)
// 合併 methods 同名覆蓋
options.methods = R.merge(parent.methods, child.methods)
// 合併 watcher 同名合併成一個陣列
options.watch = mergeWatch(parent.watch, child.watch)
// 合併 computed 同名覆蓋
options.computed = R.merge(parent.computed, child.computed)
return options
}
function mergeData(parentValue, childValue) {
if (!parentValue) {
return childValue
}
if (!childValue) {
return parentValue
}
return function mergeFnc() {
return R.merge(parentValue.call(this), childValue.call(this))
}
}
// 由於 watcher 的特殊性,我們不覆蓋同名屬性,而是都儲存在一個陣列中
function mergeWatch(parentVal, childVal) {
if (!childVal) return R.clone(parentVal || {})
let ret = R.merge({}, parentVal)
for (let key in childVal) {
let parent = ret[key]
let child = childVal[key]
if (parent && !Array.isArray(parent)) {
parent = [parent]
}
ret[key] = parent
? parent.concat(child)
: Array.isArray(child) ? child : [child]
}
return ret
}
複製程式碼
目前我們僅僅實現了 data/methods/watch/computed
這 4
個 options
中的內容,所以我們先合併這 4
項,由於 data/methods/computed
這 3
項是具有唯一性(比如 this.a
應該是一個確定的值),所以採用同名屬性覆蓋的方式,而 watch
是當發生變化時候執行方法,所以所有註冊過的方法都應該執行,因而採用同名屬性的內容合併成一個陣列。
這裡我用了 ramda 這個庫提供的合併方法,用來合併兩個物件,並不會修改原物件的內容。
extend
ok 合併 options
的方法寫好了,我們接著來實現 extend
同過上面的分析,extend
函式僅僅是對預設 options
的擴充套件
Vue.extend = function (extendOptions) {
const Super = this
class Sub extends Super {
constructor(options) {
super(options)
}
}
Sub.options = mergeOptions(
Super.options,
extendOptions
)
Sub.super = Super
Sub.extend = Super.extend
return Sub
}
複製程式碼
同樣的我們使用 mergeOptions
來合併一下 options
即可,同時將 super
指向父類、獲取 extend
方法。
測試
import {Vue} from './Vue.mjs'
let subVue = Vue.extend({
data() {
return {
dataTest: 1
}
},
methods: {
methodTest() {
console.log('methodTest')
}
},
watch: {
'dataTest'(newValue, oldValue) {
console.log('watchTest newValue = ' + newValue)
}
},
computed: {
'computedTest': {
get() {
return this.dataTest + 1
}
}
}
})
let test = new subVue({
data() {
return {
subData: 11
}
},
methods: {
subMethod() {
console.log('subMethodTest')
}
},
watch: {
'subData'(newValue, oldValue) {
console.log('subWatch newValue = ' + newValue)
}
},
computed: {
'subComputed': {
get() {
return this.subData + 1
}
}
}
})
console.log(test.dataTest)
// 1
console.log(test.subData)
// 11
console.log(test.computedTest)
// 2
console.log(test.subComputed)
// 12
test.methodTest()
// methodTest
test.subMethod()
// subMethodTest
test.dataTest = 2
// watchTest newValue = 2
test.subData = 12
// subWatch newValue = 12
console.log(test.constructor === subVue)
// true
console.log(subVue.super === Vue)
// true
複製程式碼
ok 符合我們的預期,extend
方法也就實現了,下一步,實現父子元件。