微信小程序 - mixins
mixins 概念 可百度 参考 http://ask.seowhy.com/article/21007
大意和Python中的多重继承, java中的接口类似(java接口只是定义,实现需要子类自己写).
而JS中没有接口的概念, 利用对象的key遍历,并合并到子类(或者叫对象)中.
伪代码:
var mixin = { //混入对象包含一个方法 demoFunction(){ } } ; var PageOption = { //需要继承mixin的对象 }; //内部处理mixin的逻辑 processMixins(PageOption , mixin); //最终得到的 PageOption PageOption = { demoFunction(){ } };
mixins 可以有多个,遭遇名称冲突时的规则 见下图: vue的component可以简单理解为 微信小程序的PageOption
我们的框架中,mixins的处理,集中在 utils/BasePageOptionClass 中.并且暂时内置了2个 顶层mixin对象
//全局mixins 基础库
var basePage = require("../mixins/BasePage.js");
//全局 mixins 扩展库,预留
var basePageExt = require("../mixins/BasePageExt.js");
处理方法:
/** * 处理mixins */ function _processMixins__(actualOption){ var app = getApp(); var pageOption = actualOption.PageOption; var mixins = pageOption.mixins||[]; mixins.unshift(basePage, basePageExt); var data={}; var methods = {}; var lifecycleMethods = { "onLoad": [], "onReady": [], "onShow": [], "onHide": [], "onUnload": [], "onPullDownRefresh": [], "onReachBottom": [], "onShareAppMessage": [], "onPageScroll": [] }; for (var i=0;i< mixins.length;i++){ var mixin = mixins[i]; var d = mixin.data; if(d){ app.util.extend(data,d); } var keys=Object.keys(mixin); for(key of keys){ if(key=="data")continue; if (lifecycleMethods[key] ){ //生命周期函数 if (typeof mixin[key] =="function"){ //是函数 lifecycleMethods[key].push(mixin[key]); //先加入的mixin生命周期函数先执行 } else {//不是函数, 不在规范内, 作为属性,如果Page定义,舍弃,没有则并入 if (!actualOption[key]){ //防止mixin破坏页面定义 actualOption[key]=mixin[key]; } } }else{ //普通函数 actualOption[key] = mixin[key]; } } } //合并到Page定义的data actualOption.PageOption.data = app.util.extend(data, actualOption.PageOption.data); actualOption.PageOption.__lifecycleMethods = lifecycleMethods; }
这样大致实现了 vue定义的冲突解决方案.
执行方法代码逻辑
// mixins 生命周期函数 执行方案,总是先执行 var __lifecycleMethods = actualOption.PageOption.__lifecycleMethods || {}; var __lifecycleMethod = __lifecycleMethods[key]; var mixinsRunsReturn = false; if (__lifecycleMethod && __lifecycleMethod.length>0){ for (let ii = 0; ii < __lifecycleMethod.length;ii++){ try{ var ret = __lifecycleMethod[ii].apply($target, arguments); if(ret===false){ //这里预留增加了 mixin方法的返回值,如果是false,那么逻辑终止 mixinsRunsReturn = true; break; } }catch(err){ console.log("执行mixin函数报错",err); throw err; } } } if (mixinsRunsReturn){ console.log("mixin函数返回false,执行被终止"); return; }
经过改造,原先一些定义在BasePageOptionClass中的一些方法,转移到了 /mixins/BasePage.js 中.详见代码.
拓展思考:
哪些逻辑方法适合写入mixin
目前实现的:
左划删除
顶部可滑动菜单
实现思路其实和 微信的template,component类似
因为mixin的特性,所有引入mixin类,就引入了支持 这些操作或者效果的代码.
wxml 直接在页面中书写, css 引入, 这个步骤 和组件的步骤是一致的.
优点是灵活.编码规则上,多增加点前缀,避免名称冲突
在良好的文档支持下,内部开发应该不成问题
/** * 左划需要的事件 * wxss中 增加 @import "/style/modules/left_touch.wxss"; * * 用法 Page页面增加 * let left_touch = require("../../../mixins/left_touch.js"); * PageOption 增加 mixins:[left_touch] * * wxml 示例 <view class='left_touch_block'> <view class="items"> <view wx:for="{{list}}" wx:key="{{index}}" class="touch_block_item"> <view data-data_path="list[{{index}}]._txtStyle" bindtouchstart="_touchS" bindtouchmove="_touchM" bindtouchend="_touchE" data-index="{{index}}" style="{{item._txtStyle}}" class="inner touch_block_content"> <view>{{index}}</view> <view>{{item.txt}}</view> </view> <view data-index="{{index}}" bindtap = "_btn_touched" class="inner touch_block_btn">删除</view> </view> </view> </view> 其中 data-data_path 标志 数据的层级,用户 设置单项的_txtStyle 属性,此属性为插件内部使用,外部数据不需要初始化这个属性 _btn_touched 方法需要自己写,见下方代码示例部分,根据具体逻辑编写 本插件更多的是为了基本的排版和通用事件的编写 * */ var obj = { data:{ _delBtnWidth: 180 //删除按钮宽度单位(rpx) }, onLoad: function (options) { // 页面初始化 options为页面跳转所带来的参数 this._initEleWidth(); }, /** * 触摸开始事件,记录 开始坐标 */ _touchS: function (e) { if (e.touches.length == 1) { this.setData({ //设置触摸起始点水平方向位置 _startX: e.touches[0].clientX }); } }, /** * 触摸移动中事件 * 构建简单动画, 即 删除按钮 动态展现 */ _touchM: function (e) { var that = this if (e.touches.length == 1) { //手指移动时水平方向位置 var moveX = e.touches[0].clientX; //手指起始点位置与移动期间的差值 var disX = this.data._startX - moveX; var _delBtnWidth = this.data._delBtnWidth; var txtStyle = ""; if (disX == 0 || disX < 0) {//如果移动距离小于等于0,文本层位置不变 txtStyle = "left:0px"; } else if (disX > 0) {//移动距离大于0,文本层left值等于手指移动距离 txtStyle = "left:-" + disX + "px"; if (disX >= _delBtnWidth) { //控制手指移动距离最大值为删除按钮的宽度 txtStyle = "left:-" + _delBtnWidth + "px"; } } //获取手指触摸的是哪一项 var dataset = e.currentTarget.dataset; var index = dataset.index; var data_path = dataset.data_path var updateObject = {}; updateObject[data_path] = txtStyle; this.setData(updateObject); } }, /** * 触摸结束事件 * 结合触摸开始时的坐标,算出移动的距离 * 如果距离超出删除按钮的一半,那么 算是移动成功,展示全部删除按钮,否则 删除按钮隐藏 * 注意 更新数据方式,并不是网上示例的全部数据更新 */ _touchE: function (e) { var self= this; if (e.changedTouches.length == 1) { //手指移动结束后水平位置 var endX = e.changedTouches[0].clientX; //触摸开始与结束,手指移动的距离 var disX = this.data._startX - endX; var _delBtnWidth = this.data._delBtnWidth; var index = e.currentTarget.dataset.index; var data_path = e.currentTarget.dataset.data_path; var updateObject = {}; //如果距离小于删除按钮的1/2,不显示删除按钮 var txtStyle = "left:0px"; if( disX > _delBtnWidth / 2 ){ txtStyle = "left:-" + _delBtnWidth + "px" if (self._lastMovedElePath) { updateObject[self._lastMovedElePath] = "left:0px"; } self._lastMovedElePath = data_path } else{ } //获取手指触摸的是哪一项 updateObject[data_path] = txtStyle; //更新列表的状态 this.setData(updateObject); } }, //获取元素自适应后的实际宽度 _getEleWidth: function (w) { var real = 0; try { var res = wx.getSystemInfoSync().windowWidth; var scale = (750 / 2) / (w / 2);//以宽度750px设计稿做宽度的自适应 // console.log(scale); real = Math.floor(res / scale); return real; } catch (e) { return false; // Do something when catch error } }, _initEleWidth: function () { var _delBtnWidth = this._getEleWidth(this.data._delBtnWidth); this.setData({ _delBtnWidth: _delBtnWidth }); }, /** * 点击删除按钮事件 * 这是个示例方法,请在具体的页面Page中重写此方法逻辑 */ _btn_touched: function (e) { var that = this wx.showModal({ title: '提示', content: '是否删除?', success: function (res) { if (res.confirm) { //获取列表中要删除项的下标 var index = e.target.dataset.index; // var list = that.data.list; // //移除列表中下标为index的项 // list.splice(index, 1); // //更新列表的状态 // that.setData({ // list: list // }); } else { //做页面重新渲染,或什么都不做 } } }) } }; module.exports= obj;