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


Posted in Javascript onAugust 09, 2017

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扩展

如何让小程序支持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。

总结

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对三水点靠木的支持。

Javascript 相关文章推荐
Visual Studio中的jQuery智能提示设置方法
Mar 27 Javascript
使用JS进行目录上传(相当于批量上传)
Dec 05 Javascript
基于jQuery实现的文字按钮表单特效整理
Dec 07 Javascript
jQuery中wrapInner()方法用法实例
Jan 16 Javascript
jquery转盘抽奖功能实现
Nov 13 Javascript
jQuery+canvas实现简单的球体斜抛及颜色动态变换效果
Jan 28 Javascript
JS正则子匹配实例分析
Dec 22 Javascript
javascript正则表达式模糊匹配IP地址功能示例
Jan 06 Javascript
Vue结合Video.js播放m3u8视频流的方法示例
May 04 Javascript
详解Angular中通过$location获取地址栏的参数
Aug 02 Javascript
微信小程序MUI侧滑导航菜单示例(Popup弹出式,左侧不动,右侧滑动)
Jan 23 Javascript
实现vuex原理的示例
Oct 21 Javascript
javascript编程开发中取色器及封装$函数用法示例
Aug 09 #Javascript
vuejs父子组件之间数据交互详解
Aug 09 #Javascript
Angular在模板驱动表单中自定义校验器的方法
Aug 09 #Javascript
浅谈react+es6+webpack的基础配置
Aug 09 #Javascript
js中less常用的方法小结
Aug 09 #Javascript
利用JS做网页特效_大图轮播(实例讲解)
Aug 09 #Javascript
基于Vue实例对象的数据选项
Aug 09 #Javascript
You might like
php IP及IP段进行访问限制的代码
2008/12/17 PHP
PHP和Mysqlweb应用开发核心技术 第1部分 Php基础-3 代码组织和重用2
2011/07/03 PHP
PHP 获取文件路径(灵活应用__FILE__)
2013/02/15 PHP
php数组合并array_merge()函数使用注意事项
2014/06/19 PHP
php ajax异步读取rss文档数据
2016/03/29 PHP
php如何利用pecl安装mongodb扩展详解
2019/01/09 PHP
jquery图片上下tab切换效果
2011/03/18 Javascript
jquery(live)中File input的change方法只起一次作用的解决办法
2011/10/21 Javascript
JavaScript代码复用模式实例分析
2012/12/02 Javascript
javascript中expression的用法整理
2014/05/13 Javascript
Javascript判断图片尺寸大小实例分析
2014/06/16 Javascript
jQuery跨域问题解决方案
2015/08/03 Javascript
Javascript连接Access数据库完整实例
2015/08/03 Javascript
CSS中position属性之fixed实现div居中
2015/12/14 Javascript
理解JavaScript事件对象
2016/01/25 Javascript
Javascript农历与公历相互转换的简单实例
2016/10/09 Javascript
浅谈jquery采用attr修改form表单enctype不起作用的问题
2016/11/25 Javascript
vue.js 底部导航栏 一级路由显示 子路由不显示的解决方法
2018/03/09 Javascript
JS封装的模仿qq右下角消息弹窗功能示例
2018/08/22 Javascript
[27:02]2014 DOTA2国际邀请赛中国区预选赛 5 23 CIS VS LGD第三场
2014/05/24 DOTA
Python 中 Virtualenv 和 pip 的简单用法详解
2017/08/18 Python
python: 自动安装缺失库文件的方法
2018/10/22 Python
Python netmiko模块的使用
2020/02/14 Python
django为Form生成的label标签添加class方式
2020/05/20 Python
Python使用xlrd实现读取合并单元格
2020/07/09 Python
Python中的特殊方法以及应用详解
2020/09/20 Python
python如何提升爬虫效率
2020/09/27 Python
HTML5 绘制图像(上)之:关于canvas元素引领下一代web页面的问题
2013/04/24 HTML / CSS
Omio意大利:全欧洲低价大巴、火车和航班搜索和比价
2017/12/02 全球购物
SKECHERS斯凯奇中国官网:来自美国的运动休闲品牌
2018/11/14 全球购物
税务专业毕业生自荐信
2013/11/10 职场文书
医科大学毕业生自荐信
2014/02/03 职场文书
酒店开业策划方案
2014/06/02 职场文书
初中数学教学随笔
2015/08/15 职场文书
校园之声广播稿
2015/08/18 职场文书
我国拿下天问一号火星着陆区附近 22 个地理实体命名:平乐、西柏坡、古田、漠河等
2022/04/29 数码科技