微信小程序开发教程之增加mixin扩展
mixin简介
mixin(织入)模式并不是gof的《设计模式》归纳中的一种,但是在各种语言以及框架都会发现该模式(或者思想)的一些应用。简单来说,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
/** * 为每个页面提供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; };
小结
1、本文主要讲了如何为小程序增加mixin支持。实现思路为:预处理configobj
page(createpage(configobj))
2、在处理mixin重复时,与vue保持一致:
data, methods会合并,组件自身具有最高优先级,其次mixins中后配置的mixin优先级较高。
lifecycle不会合并。先顺序执行mixins中的lifecycle,再执行组件自身的lifecycle。
总结
好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。