原文來自我的部落格:https://jrainlau.github.io/#/...
在原生開發小程式的過程中,發現有多個頁面都使用了幾乎完全一樣的邏輯。由於小程式官方並沒有提供 Mixins 這種程式碼複用機制,所以只能採用非常不優雅的複製貼上的方式去“複用”程式碼。隨著功能越來越複雜,靠複製貼上來維護程式碼顯然不科學,於是便尋思著如何在小程式裡面實現 Mixins。
什麼是 Mixins
Mixins 直譯過來是“混入”的意思,顧名思義就是把可複用的程式碼混入當前的程式碼裡面。熟悉 VueJS 的同學應該清楚,它提供了更強大了程式碼複用能力,解耦了重複的模組,讓系統維護更加方便優雅。
先看看在 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
的物件,裡面定義了一些生命週期函式和方法。接著在一個新建的元件裡面直接通過 mixins: [myMixin]
的方式注入,此時新建的元件便獲得了來自 myMixin
的方法了。
明白了什麼是 Mixins 以後,便可開始著手在小程式裡面實現了。
Mixins 的機制
Mixins 也有一些小小的細節需要注意的,就是關於生命週期事件的執行順序。在上一節的例子中,我們在 myMixin
裡定義了一個 created()
方法,這是 VueJS 裡面的一個生命週期事件。如果我們在新建元件 Component
裡面也定義一個 created()
方法,那麼執行結果會是如何呢?
var Component = Vue.extend({
mixins: [myMixin],
created: function () {
console.log('hello from Component!')
}
})
var component = new Component()
// =>
// Hello from mixin!
// Hello from Component!
可以看執行結果是先輸出了來自 Mixin 的 log,再輸出來自元件的 log。
除了生命週期函式以外,再看看物件屬性的混入結果:
// 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 和元件的物件屬性當中的內容(如 data
, methods
等)混合,以確保兩邊的資料都同時存在。
經過上述的驗證,我們可以得到 VueJS 中關於 Mixins 執行機制的結論:
- 生命週期屬性,會優先執行來自 Mixins 當中的,後執行來自元件當中的。
- 物件型別屬性,來自 Mixins 和來自元件中的會共存。
但是在小程式中,這套機制會和 VueJS 的有一點區別。在小程式中,自定義的方法是直接定義在 Page 的屬性當中的,既不屬於生命週期型別屬性,也不屬於物件型別屬性。為了不引入奇怪的問題,我們為小程式的 Mixins 執行機制多加一條:
- 小程式中的自定義方法,優先順序為 Page > Mixins,即 Page 中的自定義方法會覆蓋 Mixins 當中的。
程式碼實現
在小程式中,每個頁面都由 Page(options)
函式定義,而 Mixins 則作用於這個函式當中的 options
物件。因此我們實現 Mixins 的思路就有了——劫持並改寫 Page
函式,最後再重新把它釋放出來。
新建一個 mixins.js
檔案:
// 儲存原生的 Page 函式
const originPage = Page
Page = (options) => {
const mixins = options.mixins
// mixins 必須為陣列
if (Array.isArray(mixins)) {
delete options.mixins
// mixins 注入並執行相應邏輯
options = merge(mixins, options)
}
// 釋放原生 Page 函式
originPage(options)
}
原理很簡單,關鍵的地方在於 merge()
函式。merge
函式即為小程式 Mixins 執行機制的具體實現,完全按照上一節總結的三條結論來進行。
// 定義小程式內建的屬性/方法
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)) {
// 內建物件屬性混入
options[key] = { ...value, ...options[key] }
} else if (originMethods.includes(key)) {
// 內建方法屬性混入,優先執行混入的部分
const originFunc = options[key]
options[key] = function (...args) {
value.call(this, ...args)
return originFunc && originFunc.call(this, ...args)
}
} else {
// 自定義方法混入
options = { ...mixin, ...options }
}
}
})
return options
}
Mixins 使用
-
在小程式的
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')] })
大功告成!此時小程式已經具備 Mixins 的能力,對於程式碼解耦與複用來說將會更加方便。