为微信小程序增加mixin扩展

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.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这个值,可以直接在模板中使用。
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'
  }
})

这里有两个点,我们要特别注意:
Page(configObj) - 通过configObj配置小程序页面的data, method, lifecycle等
onLoad方法 - 页面main入口

要想让mixin中的定义有效,就要在configObj正式传给Page()之前做文章。其实Page(configObj)就是一个普通的函数调用,我们加个中间方法:

Page(createPage(configObj))

在createPage这个方法中,我们可以预处理configObj中的mixin,把其中的配置按正确的方式合并到configObj上,最后交给Page()。这就是实现mixin的思路。

具体实现

具体代码实现就不再赘述,可以看下面的代码。更详细的代码实现,更多扩展,测试可以参看[github]
(https://github.com/ghjagus/wx-mixin)

/**
 * 为每个页面提供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。

原文地址:http://www.wxapp-union.com/article-2824-1.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,602评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,442评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,878评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,306评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,330评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,071评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,382评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,006评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,512评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,965评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,094评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,732评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,283评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,286评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,512评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,536评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,828评论 2 345

推荐阅读更多精彩内容

  • Mixin这个概念在React, Vue中都有支持,它为我们抽象业务逻辑,代码复用提供了方便。然而小程序原生框架并...
    第九程序官方阅读 1,785评论 0 5
  • 基于Vue的一些资料 内容 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 element★...
    尝了又尝阅读 1,140评论 0 1
  • 前几天看到一张很暖心的国外朋友圈照片。 一位患有忧郁症的姐姐,万念俱灰,想结束自己的生命。准备齐全后,她却看到自己...
    她在丛中笑阅读 254评论 0 2
  • (一)湖北省谷城县实验中学原校长程泽友,是我的老朋友。下面这幅“富贵长春图”就是应嘱而画的,规格为四尺宣。赠与之前...
    高德华阅读 700评论 1 9
  • 在简书的这段期间,我坚持日更,写着写着就发现自己真的就是视野狭窄,储存信息量少得可怜,每天要做到日更,就必须坚持看...
    澜绣阅读 297评论 0 1