微信小程序開發-增長mixin擴展

Mixin這個概念在React, Vue中都有支持,它爲咱們抽象業務邏輯,代碼複用提供了方便。然而小程序原生框架並沒直接支持Mixin。咱們先看一個很實際的需求:vue

爲全部小程序頁面增長運行環境class,以方便作一些樣式hack。具體說就是小程序在不一樣的運行環境(開發者工具|iOS|Android)運行時,platform值爲對應的運行環境值("ios|android|devtools")react

<view class="{{platform}}">
   <!--頁面模板-->
</view>

回顧vue中mixin的使用

文章開始提到的問題是很是適合使用Mixin來解決的。咱們把這個需求轉換成一個Vue問題:在每一個路由頁面中增長一個platform的樣式class(雖然這樣作可能沒實際意義)。實現思路就是爲每一個路由組件增長一個data: platform。代碼實現以下:android

// mixins/platform.js
const getPlatform = () => {
  // 具體實現略,這裏mock返回'ios'
  return 'ios';
};

export default {
  data() {
    return {
      platform: getPlatform()
    }
  }
}
// 在路由組件中使用
// views/index.vue
import platform from 'mixins/platform';

export default {
  mixins: [platform],
  // ...
}
// 在路由組件中使用
// views/detail.vue
import platform from 'mixins/platform';

export default {
  mixins: [platform],
  // ...
}

這樣,在index,detail兩個路由頁的viewModel上就都有platform這個值,能夠直接在模板中使用。ios

vue中mixin分類

  • data mixin
  • normal method mixin
  • lifecycle method mixin

用代碼表示的話,就如:git

export default {
  data () {
    return {
      platform: 'ios'
    }
  },

  methods: {
    sayHello () {
      console.log(`hello!`)
    }
  },

  created () {
    console.log(`lifecycle3`)
  }
}

vue中mixin合併,執行策略

若是mixin間出現了重複,這些mixin會有具體的合併,執行策略。以下圖:github

如何讓小程序支持mixin

在前面,咱們回顧了vue中mixin的相關知識。如今咱們要讓小程序也支持mixin,實現vue中同樣的mixin功能。redux

實現思路

咱們先看一下官方的小程序頁面註冊方式:小程序

Page({
  data: {
    text: "This is page data."
  },
  onLoad: function(options) {
    // Do some initialize when page load.
  },
  onReady: function() {
    // Do something when page ready.
  },
  onShow: function() {
    // Do something when page show.
  },
  onHide: function() {
    // Do something when page hide.
  },
  onUnload: function() {
    // Do something when page close.
  },
  customData: {
    hi: 'MINA'
  }
})

假如咱們加入mixin配置,上面的官方註冊方式會變成:數組

Page({
  mixins: [platform],
  data: {
    text: "This is page data."
  },
  onLoad: function(options) {
    // Do some initialize when page load.
  },
  onReady: function() {
    // Do something when page ready.
  },
  onShow: function() {
    // Do something when page show.
  },
  onHide: function() {
    // Do something when page hide.
  },
  onUnload: function() {
    // Do something when page close.
  },
  customData: {
    hi: 'MINA'
  }
})

這裏有兩個點,咱們要特別注意:app

  1. Page(configObj) - 經過configObj配置小程序頁面的data, method, lifecycle等
  2. onLoad方法 - 頁面main入口

要想讓mixin中的定義有效,就要在configObj正式傳給Page()以前作文章。其實Page(configObj)就是一個普通的函數調用,咱們加個中間方法:

Page(createPage(configObj))

在createPage這個方法中,咱們能夠預處理configObj中的mixin,把其中的配置按正確的方式合併到configObj上,最後交給Page()。這就是實現mixin的思路。

具體實現

具體代碼實現就再也不贅述,能夠看下面的代碼。更詳細的代碼實現,更多擴展,測試能夠參看github

/**
 * 爲每一個頁面提供mixin,page invoke橋接
 */

const isArray = v => Array.isArray(v);
const isFunction = v => typeof v === 'function';
const noop = function () {};

// 借鑑redux https://github.com/reactjs/redux
function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg;
  }

  if (funcs.length === 1) {
    return funcs[0];
  }

  const last = funcs[funcs.length - 1];
  const rest = funcs.slice(0, -1);
  return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args));
}


// 頁面堆棧
const pagesStack = getApp().$pagesStack;

const PAGE_EVENT = ['onLoad', 'onReady', 'onShow', 'onHide', 'onUnload', 'onPullDownRefresh', 'onReachBottom', 'onShareAppMessage'];
const APP_EVENT = ['onLaunch', 'onShow', 'onHide', 'onError'];

const onLoad = function (opts) {
  // 把pageModel放入頁面堆棧
  pagesStack.addPage(this);

  this.$invoke = (pagePath, methodName, ...args) => {
    pagesStack.invoke(pagePath, methodName, ...args);
  };

  this.onBeforeLoad(opts);
  this.onNativeLoad(opts);
  this.onAfterLoad(opts);
};

const getMixinData = mixins => {
  let ret = {};

  mixins.forEach(mixin => {
    let { data={} } = mixin;

    Object.keys(data).forEach(key => {
      ret[key] = data[key];
    });
  });

  return ret;
};

const getMixinMethods = mixins => {
  let ret = {};

  mixins.forEach(mixin => {
    let { methods={} } = mixin;

    // 提取methods
    Object.keys(methods).forEach(key => {
      if (isFunction(methods[key])) {
        // mixin中的onLoad方法會被丟棄
        if (key === 'onLoad') return;

        ret[key] = methods[key];
      }
    });

    // 提取lifecycle
    PAGE_EVENT.forEach(key => {
      if (isFunction(mixin[key]) && key !== 'onLoad') {
        if (ret[key]) {
          // 多個mixin有相同lifecycle時,將方法轉爲數組存儲
          ret[key] = ret[key].concat(mixin[key]);
        } else {
          ret[key] = [mixin[key]];
        }
      }
    })
  });

  return ret;
};

/**
 * 重複衝突處理借鑑vue:
 * data, methods會合並,組件自身具備最高優先級,其次mixins中後配置的mixin優先級較高
 * lifecycle不會合並。先順序執行mixins中的lifecycle,再執行組件自身的lifecycle
 */

const mixData = (minxinData, nativeData) => {
  Object.keys(minxinData).forEach(key => {
    // page中定義的data不會被覆蓋
    if (nativeData[key] === undefined) {
      nativeData[key] = minxinData[key];
    }
  });

  return nativeData;
};

const mixMethods = (mixinMethods, pageConf) => {
  Object.keys(mixinMethods).forEach(key => {
    // lifecycle方法
    if (PAGE_EVENT.includes(key)) {
      let methodsList = mixinMethods[key];

      if (isFunction(pageConf[key])) {
        methodsList.push(pageConf[key]);
      }

      pageConf[key] = (function () {
        return function (...args) {
          compose(...methodsList.reverse().map(f => f.bind(this)))(...args);
        };
      })();
    }

    // 普通方法
    else {
      if (pageConf[key] == null) {
        pageConf[key] = mixinMethods[key];
      }
    }
  });

  return pageConf;
};

export default pageConf => {

  let {
    mixins = [],
    onBeforeLoad = noop,
    onAfterLoad = noop
  } = pageConf;

  let onNativeLoad = pageConf.onLoad || noop;
  let nativeData = pageConf.data || {};

  let minxinData = getMixinData(mixins);
  let mixinMethods = getMixinMethods(mixins);

  Object.assign(pageConf, {
    data: mixData(minxinData, nativeData),
    onLoad,
    onBeforeLoad,
    onAfterLoad,
    onNativeLoad,
  });

  pageConf = mixMethods(mixinMethods, pageConf);

  return pageConf;
};

小結

  • 本文主要講了如何爲小程序增長mixin支持。實現思路爲:預處理configObj
Page(createPage(configObj))
  • 在處理mixin重複時,與vue保持一致:

    • data, methods會合並,組件自身具備最高優先級,其次mixins中後配置的mixin優先級較高。
    • lifecycle不會合並。先順序執行mixins中的lifecycle,再執行組件自身的lifecycle。
相關文章
相關標籤/搜索