實現 VUE 中 MVVM - step11 - Extend
在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
方法也就實現了,下一步,實現父子元件。