微信小程序开发-增加mixin扩展

来源:互联网 发布:仓储流程优化的方法 编辑:程序博客网 时间:2024/06/01 13:42

Mixin这个概念在React, Vue中都有支持,它为我们抽象业务逻辑,代码复用提供了方便。然而小程序原生框架并没直接支持Mixin。我们先看一个很实际的需求:

为所有小程序页面增加运行环境class,以方便做一些样式hack。具体说就是小程序在不同的运行环境(开发者工具|iOS|Android)运行时,platform值为对应的运行环境值("ios|android|devtools")

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

回顾vue中mixin的使用

文章开始提到的问题是非常适合使用Mixin来解决的。我们把这个需求转换成一个Vue问题:在每个路由页面中增加一个platform的样式class(虽然这样做可能没实际意义)。实现思路就是为每个路由组件增加一个data: platform。代码实现如下:

// mixins/platform.jsconst getPlatform = () => {  // 具体实现略,这里mock返回'ios'  return 'ios';};export default {  data() {    return {      platform: getPlatform()    }  }}
// 在路由组件中使用// views/index.vueimport platform from 'mixins/platform';export default {  mixins: [platform],  // ...}
// 在路由组件中使用// views/detail.vueimport platform from 'mixins/platform';export default {  mixins: [platform],  // ...}

这样,在index,detail两个路由页的viewModel上就都有platform这个值,可以直接在模板中使用。

vue中mixin分类

  • data mixin
  • normal method mixin
  • lifecycle method mixin

用代码表示的话,就如:

export default {  data () {    return {      platform: 'ios'    }  },  methods: {    sayHello () {      console.log(`hello!`)    }  },  created () {    console.log(`lifecycle3`)  }}

vue中mixin合并,执行策略

如果mixin间出现了重复,这些mixin会有具体的合并,执行策略。如下图:

如何让小程序支持mixin

在前面,我们回顾了vue中mixin的相关知识。现在我们要让小程序也支持mixin,实现vue中一样的mixin功能。

实现思路

我们先看一下官方的小程序页面注册方式:

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'  }})

这里有两个点,我们要特别注意:

  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/reduxfunction 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。