在原生開發(fā)小程序的過程中,發(fā)現(xiàn)有多個頁面都使用了幾乎完全一樣的邏輯。由于小程序官方并沒有提供 Mixins 這種代碼復(fù)用機制,所以只能采用非常不優(yōu)雅的復(fù)制粘貼的方式去“復(fù)用”代碼。隨著功能越來越復(fù)雜,靠復(fù)制粘貼來維護代碼顯然不科學(xué),于是便尋思著如何在小程序里面實現(xiàn) Mixins。
Mixins 直譯過來是“混入”的意思,顧名思義就是把可復(fù)用的代碼混入當(dāng)前的代碼里面。熟悉 VueJS 的同學(xué)應(yīng)該清楚,它提供了更強大了代碼復(fù)用能力,解耦了重復(fù)的模塊,讓系統(tǒng)維護更加方便優(yōu)雅。
先看看在 VueJS 中是怎么使用 Mixins 的。
// define a mixin object
var myMixin = {
created: function () {
this.hello()
},
methods: {
hello: function () {
console.log('hello from mixin!')
}
}
}
// define a component that uses this mixin
var Component = Vue.extend({
mixins: [myMixin]
})
var component = new Component() // => "hello from mixin!"
|
在上述的代碼中,首先定義了一個名為 myMixin 的對象,里面定義了一些生命周期函數(shù)和方法。接著在一個新建的組件里面直接通過 mixins: [myMixin] 的方式注入,此時新建的組件便獲得了來自 myMixin 的方法了。
明白了什么是 Mixins 以后,便可開始著手在小程序里面實現(xiàn)了。
Mixins 也有一些小小的細節(jié)需要注意的,就是關(guān)于生命周期事件的執(zhí)行順序。在上一節(jié)的例子中,我們在 myMixin 里定義了一個 created() 方法,這是 VueJS 里面的一個生命周期事件。如果我們在新建組件 Component 里面也定義一個 created() 方法,那么執(zhí)行結(jié)果會是如何呢?
var Component = Vue.extend({
mixins: [myMixin],
created: function () {
console.log('hello from Component!')
}
})
var component = new Component()
// =>
// Hello from mixin!
// Hello from Component!
|
可以看運行結(jié)果是先輸出了來自 Mixin 的 log,再輸出來自組件的 log。
除了生命周期函數(shù)以外,再看看對象屬性的混入結(jié)果:
// define a mixin object
const myMixin = {
data () {
return {
mixinData: 'data from mixin'
}
}
}
// define a component that uses this mixin
var Component = Vue.extend({
mixins: [myMixin],
data () {
return {
componentData: 'data from component'
}
},
mounted () {
console.log(this.$data)
}
})
var component = new Component()
|
在 VueJS 中,會把來自 Mixins 和組件的對象屬性當(dāng)中的內(nèi)容(如 data, methods等)混合,以確保兩邊的數(shù)據(jù)都同時存在。
經(jīng)過上述的驗證,我們可以得到 VueJS 中關(guān)于 Mixins 運行機制的結(jié)論:
但是在小程序中,這套機制會和 VueJS 的有一點區(qū)別。在小程序中,自定義的方法是直接定義在 Page 的屬性當(dāng)中的,既不屬于生命周期類型屬性,也不屬于對象類型屬性。為了不引入奇怪的問題,我們?yōu)樾〕绦虻?Mixins 運行機制多加一條:
在小程序中,每個頁面都由 Page(options) 函數(shù)定義,而 Mixins 則作用于這個函數(shù)當(dāng)中的 options 對象。因此我們實現(xiàn) Mixins 的思路就有了——劫持并改寫 Page 函數(shù),最后再重新把它釋放出來。
新建一個 mixins.js 文件:
// 保存原生的 Page 函數(shù)
const originPage = Page
Page = (options) => {
const mixins = options.mixins
// mixins 必須為數(shù)組
if (Array.isArray(mixins)) {
delete options.mixins
// mixins 注入并執(zhí)行相應(yīng)邏輯
options = merge(mixins, options)
}
// 釋放原生 Page 函數(shù)
originPage(options)
}
|
原理很簡單,關(guān)鍵的地方在于 merge() 函數(shù)。merge 函數(shù)即為小程序 Mixins 運行機制的具體實現(xiàn),完全按照上一節(jié)總結(jié)的三條結(jié)論來進行。
// 定義小程序內(nèi)置的屬性/方法
const originProperties = ['data', 'properties', 'options']
const originMethods = ['onLoad', 'onReady', 'onShow', 'onHide', 'onUnload', 'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage', 'onPageScroll', 'onTabItemTap']
function merge (mixins, options) {
mixins.forEach((mixin) => {
if (Object.prototype.toString.call(mixin) !== '[object Object]') {
throw new Error('mixin 類型必須為對象!')
}
// 遍歷 mixin 里面的所有屬性
for (let [key, value] of Object.entries(mixin)) {
if (originProperties.includes(key)) {
// 內(nèi)置對象屬性混入
options[key] = { ...value, ...options[key] }
} else if (originMethods.includes(key)) {
// 內(nèi)置方法屬性混入,優(yōu)先執(zhí)行混入的部分
const originFunc = options[key]
options[key] = function (...args) {
value.call(this, ...args)
return originFunc && originFunc.call(this, ...args)
}
} else {
// 自定義方法混入
options = { ...mixin, ...options }
}
}
})
return options
}
|
在小程序的 app.js 里引入 mixins.js
require('./mixins.js')
撰寫一個 myMixin.js
module.exports = {
data: { someData: 'myMixin' },
onShow () { console.log('Log from mixin!') }
}
在 page/index/index.js 中使用
Page({
mixins: [require('../../myMixin.js')]
})

大功告成!此時小程序已經(jīng)具備 Mixins 的能力,對于代碼解耦與復(fù)用來說將會更加方便。