在小程序中實現 Mixins 方案

原文來自我的博客:https://jrainlau.github.io/#/...

image

在原生開發小程序的過程中,發現有多個頁面都使用了幾乎完全一樣的邏輯。由於小程序官方並沒有提供 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()

image

在 VueJS 中,會把來自 Mixins 和組件的對象屬性當中的內容(如 data, methods等)混合,以確保兩邊的數據都同時存在。

經過上述的驗證,我們可以得到 VueJS 中關於 Mixins 運行機制的結論:

  1. 生命週期屬性,會優先執行來自 Mixins 當中的,後執行來自組件當中的。
  2. 對象類型屬性,來自 Mixins 和來自組件中的會共存。

但是在小程序中,這套機制會和 VueJS 的有一點區別。在小程序中,自定義的方法是直接定義在 Page 的屬性當中的,既不屬於生命週期類型屬性,也不屬於對象類型屬性。爲了不引入奇怪的問題,我們爲小程序的 Mixins 運行機制多加一條:

  1. 小程序中的自定義方法,優先級爲 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 注入並執行相應邏輯
    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 }
      }
    }
  })
}

Mixins 使用

  1. 在小程序的 app.js 裏引入 mixins.js

    require('./mixins.js')
  2. 撰寫一個 myMixin.js

    module.exports = {
      data: { someData: 'myMixin' },
      onShow () { console.log('Log from mixin!') }
    }
  3. page/index/index.js 中使用

    Page({
      mixins: [require('../../myMixin.js')]
    })

image

大功告成!此時小程序已經具備 Mixins 的能力,對於代碼解耦與複用來說將會更加方便。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章