微信小程序 - 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;

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

 

posted @ 2017-11-18 12:09  jifsu  阅读(1054)  评论(0编辑  收藏  举报