小程序自定义组件开发及程序组件化模块化开发指南

        小程序提供了模块化,自定义组件,模板,分包加载支持,便于小程序团队开发,实现基础功能模块化、组件化,业务功能分包化,团队成员各司其职,从而有效提升代码重用性和开发效率及质量。

        一、JS模块化

      js模块化是形式,实质则是js代码重用性,一个地方写,N个地方用,基于这个思想,JS模块化有2个实现方式:

      1. 写在app.js里,不管是数据,还是函数,其它页面只要getApp(),即可调用;

      2. 写在一个单独js文件里,然后需要引用的地方,require进来;

      建议通用性强并且可以简单实现的代码块,放到app.js, 否则请单独创建js文件, 然后require。

       案例1:app.js 中实现的wx.showToast封装

        小程序自定义组件开发及程序组件化模块化开发指南_第1张图片

        上图,左侧为app.js中封装的app.toast调用后的结果,代码如下:

  toast(title, cb ) {
    let duration = 2000;
    wx.showToast({
      title: title,
      icon: "none",
      duration: duration
    });
    if (cb && typeof cb === 'function' ) {
      setTimeout(cb, duration);
    }
  },
//调用代码
//app.toast("URL已成功复制,请在浏览器中打开!");

上图右侧为wx.showToast直接调用的默认效果

        wx.showToast({
          title: 'URL已成功复制,请在浏览器中打开!',
        })

        因为,toast使用率极高,且通常情况下只要文本显示效果,所以利用showToast函数,设置icon为none封装了个toast函数,其它地方使用就很简单啦,app.toast(str)即可,并且增加了toast消失的回调处理。

    案例2:单独创建js文件,利用require引入并调用。

    由于项目需要,写了个/utils/utils.js, 包含StrUtil:format等字符串常用函数,DateUtil:日期format,parse等常用函数,代码如下:

/**
 * StringUtil
 * --format("{0}, say {1}!", "Marcus", "hello");
 * --format("name:{name}",{name:"mb"})
 */
var StrUtil = (function () {
  var _format = function () {
    if (arguments.length == 0) return null;
    var str = arguments[0], a = arguments.length > 1 ? arguments[1] : null;
    if (!a || typeof a != 'object') {
      for (var i = 1; i < arguments.length; i++) {
        var re = new RegExp('\\{' + (i - 1) + '\\}', 'gm');
        str = str.replace(re, arguments[i]);
      }
    } else {	//对象格式化.
      var r = new RegExp('\\{([\\s\\S]+?)\\}', 'gm'), b = null, _str = str;
      while ((b = r.exec(_str)) !== null) {
        str = str.replace(b[0], a[b[1]]);
      }
    }
    return str;
  },_isEmpty = function (str) {
    if (str === undefined || str === null) return !0;
    if (str === 0) return !1;
    str = str + '';
    return !!str ? (str.trim() == "" || str == "null") : !0;
  }, _ltrim = function (str, c = ' ') {
    str += '';
    return str.replace(new RegExp("^(" + c + ")+", "g"), "");
  }, _rtrim = function (str, c = ' ') {
    str += '';
    return str.replace(new RegExp("(" + c + ")+$", "g"), "");
  }, _toInteger = function (str, val = 0) {
    let value = val;
    try {
      value = parseInt(str - 0);
    } catch (e) { }
    return isNaN(value) ? val : value;
  }, _toFloat = function (str, val = 0) {
    let value = val;
    try {
      value = parseFloat(str - 0);
    } catch (e) { }
    return isNaN(value)? val : value;
  }, _toFixed = function (num, scale = 2) {
    if (num === undefined || num == null || isNaN(num)) return num;
    let result = (num - 0).toFixed(scale);
    return result ? result.replace(/\.?0+$/, '') : result;
  };
  return {
    format: _format,
    isEmpty: _isEmpty,
    ltrim: _ltrim,
    rtrim: _rtrim,
    toInteger: _toInteger,
    toFloat: _toFloat,
    toFixed: _toFixed
  };
})();

var DateUtil = (function () {
  var _fp = "yyyy-MM-dd hh:mm:ss", _sp = "yyyy-MM-dd";
  /**   
    *转换日期对象为日期字符串   
    * @param l long值   
    * @param pattern 格式字符串,例如:yyyy-MM-dd hh:mm:ss   
    * @return 符合要求的日期字符串   
    */
  var _format = function (date, format = "yyyy-MM-dd") {
    date = date || new Date;
    var o = {
      "M+": date.getMonth() + 1,
      "d+": date.getDate(),
      "h+": date.getHours(),
      "m+": date.getMinutes(),
      "s+": date.getSeconds(),
      "q+": Math.floor((date.getMonth() + 3) / 3),
      "S": date.getMilliseconds()
    };
    if (/(y+)/.test(format)) {
      format = format.replace(RegExp.$1, (date.getFullYear() + "").substr(4 - RegExp.$1.length));
    }
    for (var k in o) {
      if (new RegExp("(" + k + ")").test(format)) {
        format = format.replace(RegExp.$1, RegExp.$1.length == 1 ? o[k] : ("00" + o[k]).substr(("" + o[k]).length));
      }
    }
    return format;
  },
    //字符串转日期
    _parse = function (value) {
      let t = 0;
      if (value) {
        if (value instanceof Date) return value;
        if (isNaN(value)) {
          if (value.split("-").length == 2) value += "-01"; //yyyy-mm
          t = Date.parse(value.replace(/-/g, "/"));
        } else { t = value - 0; }
      }
      return t == 0 ? new Date() : new Date(t);
    }, _addMonth = function (date, num) {
      num = parseInt(num), date = (date instanceof Date) ? date : _parse(date);
      let y = date.getFullYear(), m = date.getMonth(), d = date.getDate();
      return new Date(y, m + num, d);
    }, _addDay = function (date, num) {
      num = parseInt(num), date = (date instanceof Date) ? date : _parse(date);
      let y = date.getFullYear(), m = date.getMonth(), d = date.getDate();
      return new Date(y, m, d + num);
    }, _isLeapYear = function (year) {
      return (year % 400 == 0) || (year % 4 == 0 && year % 100 != 0);
    }, _getMonthDays = function (year, month) {
      return [0, 31, null, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month] || (_isLeapYear(year) ? 29 : 28);
    },
    /**
     * 获取某年的周数.
     */
    _getWeeks = function (year) {
      var days = _isLeapYear(year) ? 366 : 365;
      var w = new Date(year, 0, 1).getDay() || 7, w_others = days - (7 - w + 1);
      return Math.ceil(w_others / 7) + 1;
    },
    /**
     * 获取某年第几周的开始日期.
     * 不传参数取本周开始日期.
     */
    _getWeekFisrtDay = function (year = 0, week = 0) {
      let now = new Date;
      if (year < 2000 || week < 1) {
        year = now.getFullYear()
        week = this.getWeekNumber(now);
      }
      var d = new Date(year, 0, 1), w = d.getDay() || 7;
      return week > 1 ? _addDay(d, 7 * (week - 1) - w + 1) : d;
    },
    //获取某年第几周的结束日期
    _getWeekLastDay = function (year = 0, week = 0) {
      year = year - 0, week = week - 0;
      var firstDay = this.getWeekFisrtDay(year, week), w = firstDay.getDay() || 7;
      return _addDay(firstDay, 7 - w);
    },
    /**
     * 获取某年的某天是第几周, 日期 或 字符串
     */
    _getWeekNumber = function (now) {
      now = this.parse(now);
      var year = now.getFullYear(), month = now.getMonth(), days = now.getDate();
      //那一天是那一年中的第多少天
      for (var i = 0; i < month; i++) {
        days += _getMonthDays(year, i + 1);
      }
      //那一年第一天是星期几,周日=周七
      var yearFirstDay = new Date(year, 0, 1).getDay() || 7, week = null;
      if (yearFirstDay == 1) {
        week = Math.ceil(days / yearFirstDay);
      } else {
        days -= (7 - yearFirstDay + 1), week = Math.ceil(days / 7) + 1;
      }
      return week;
    }, _getMonthFstDay = function (date) {
      return _format(_parse(date), "yyyy-MM") + "-01";
    }, _getMonthLstDay = function (date) {
      date = DateUtil.parse(date);
      var year = date.getFullYear(), month = date.getMonth();
      return _format(new Date(year, month, _getMonthDays(year, month + 1)), _sp);
    };

  return {
    format: _format,
    getSmpDate: function (date) {
      return _format(date, _sp);
    },
    getFullDate: function (date) {
      return _format(date, _fp);
    },
    parse: _parse,
    addMonth: _addMonth,
    addDay: _addDay,
    isLeapYear: _isLeapYear,
    getWeeks: _getWeeks,
    getWeekFisrtDay: _getWeekFisrtDay,
    getWeekLastDay: _getWeekLastDay,
    getMonthDays: _getMonthDays,
    getWeekNumber: _getWeekNumber,
    getMonthFstDay: _getMonthFstDay,
    getMonthLstDay: _getMonthLstDay,
  };
})();
module.exports = {StrUtil, DateUtil}

由于是Util函数,其它页面使用频率极高,因此直接在app.js引入:

let utils = require ( "utils/util.js" );

其它页面调用则只要获取app,调用app.utils.DateUtil即可,如:

var app = getApp(), DateUtil = app.utils.DateUtil;
Page({

  /**
   * 页面的初始数据
   */
  data: {
    rec_date: DateUtil.getSmpDate(new Date()),
    end_date: DateUtil.getSmpDate(new Date()),
    rec_steps: 0,
  },

二、组件化

两种实现方式,其一是通过早期的模板变相实现,另外一个则是后来推出的自定义组件。

1、通过模板实现

1)定义模板:wxml,wxss和js文件,如一个消息提示组件,toptips

/components/tmp_toptips/toptips.wxml

/components/tmp_toptips/toptips.wxss
@-webkit-keyframes notify-downin {
	0% {
		opacity: 0;
		-webkit-transform: translate3d(0, -50px, 0);
		transform: translate3d(0, -50px, 0)
	}
	to {
		opacity: 1;
		-webkit-transform: translateZ(0);
		transform: translateZ(0)
	}
}
@keyframes notify-downin {
	0% {
		opacity: 0;
		-webkit-transform: translate3d(0, -50px, 0);
		transform: translate3d(0, -50px, 0)
	}
	to {
		opacity: 1;
		-webkit-transform: translateZ(0);
		transform: translateZ(0)
	}
}
.my-animate-notify-downin {
    -webkit-animation: notify-downin .3s linear forwards;
    animation: notify-downin .3s linear forwards
}
@-webkit-keyframes notify-upout {
	0% {
		opacity: 1;
		-webkit-transform: translateZ(0);
		transform: translateZ(0)
	}
	to {
		opacity: 0;
		-webkit-transform: translate3d(0, -50px, 0);
		transform: translate3d(0, -50px, 0)
	}
}
@keyframes notify-upout {
	0% {
		opacity: 1;
		-webkit-transform: translateZ(0);
		transform: translateZ(0)
	}
	to {
		opacity: 0;
		-webkit-transform: translate3d(0, -50px, 0);
		transform: translate3d(0, -50px, 0)
	}
}
.my-animate-notify-upout{
    -webkit-animation: notify-upout .3s linear forwards;
    animation: notify-upout .3s linear forwards
}
.my-toptips {
  position: fixed; z-index: 99; padding: 20rpx 30rpx; box-sizing: border-box;
  color: #fff; font-size: 30rpx; line-height: 40rpx;
  width: 100%; top:0; display: flex; flex-direction: row;
}
.my-toptips_success { background-color: #09bb08 !important;}
.my-toptips_info {background-color: #10aefe !important;}
.my-toptips_warn {background-color: #ffbe00 !important;}
.my-toptips_cancel {background-color: #e74341 !important;}
.my-toptips_icon {margin-right: 12rpx;width:40rpx; height:40rpx;}
.my-toptips text{flex:1; line-height:1.375}

/components/tmp_toptips/toptips.js

/**
 * 模块化组件
 * @param {Object} options 配置项
 * @param {String} options.scope 组件的命名空间
 * @param {Object} options.data 组件的动态数据
 * @param {Object} options.methods 组件的事件函数
 */
class TopTips {
  constructor(options = {}) {
    this.options = Object.assign({
      scope: "$my.toptips",
      icon: `cancel`,
      hidden: !1,
      text: ``,
      timer: 3000
    }, options);
    this.page = getCurrentPages()[getCurrentPages().length - 1]
    this.setData = this.page.setData.bind(this.page)
    //初始化组件状态
    this.setData({
      [`${this.options.scope}.icon`]: this.options.icon,
      [`${this.options.scope}.text`]: this.options.text
    });
  }

  /**
	 * 设置元素显示
	 */
  setVisible() {
    this.setData({
      [`${this.options.scope}.visible`]: !0,
      [`${this.options.scope}.animation`]: 'my-animate-notify-downin'
    })
  }

  /**
	 * 设置元素隐藏
	 */
  setHidden(timer = 300) {
    // this.setData({
    //   [`${this.options.scope}.animateCss`]: className,
    // })
    setTimeout(() => {
      this.setData({
        [`${this.options.scope}.visible`]: !1,
        [`${this.options.scope}.animation`]: 'my-animate-notify-upout'
      })
    }, timer)
  }
}

export default {
	show(opts = {}) {
		// 实例化组件
    const topTips = new TopTips(opts);
    topTips.setHidden(topTips.options.timer);
    topTips.setVisible();
	}
}

2)目标页面引入模板

        wxml文件,引入模板wxml