小程序提供了模块化,自定义组件,模板,分包加载支持,便于小程序团队开发,实现基础功能模块化、组件化,业务功能分包化,团队成员各司其职,从而有效提升代码重用性和开发效率及质量。
一、JS模块化
js模块化是形式,实质则是js代码重用性,一个地方写,N个地方用,基于这个思想,JS模块化有2个实现方式:
1. 写在app.js里,不管是数据,还是函数,其它页面只要getApp(),即可调用;
2. 写在一个单独js文件里,然后需要引用的地方,require进来;
建议通用性强并且可以简单实现的代码块,放到app.js, 否则请单独创建js文件, 然后require。
案例1:app.js 中实现的wx.showToast封装
上图,左侧为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引入:
其它页面调用则只要获取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
{{ text }}
/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
wxss文件,引入模板wxss
@import '/components/tmp_toptips/toptips.wxss';
js文件,引入模板js
import $toptips from '../../../components/tmp_toptips/toptips'
3)目标页面js调用
$toptips.show({text:"模板方式调用!"});
2、自定义组件
1)定义自定义组件,跟普通页面文件构成一样,由json,wxml,wxss,js4个文件构成,最大区别在于js文件是都有的自定义组件结构,还是以上述的消息提示组件,toptips为例:
/components/toptips/toptips.json设置component为true
{
"component": true
}
/components/toptips/toptips.wxml,自定义组件显示内容
{{ text }}
/components/toptips/toptips.wxss,样式,同/components/tmp_toptips/toptips.wxss
/components/toptips/toptips.js,js文件
// components/component-tag-name.js
Component({
/**
* 组件的属性列表
*/
properties: {
text: { type: String, value: '提示文本'},
icon: {
type: String,
value: 'cancel'
},
timer: {
type: Number,
value: 3000
},
hidden: {
type: Boolean,
value: false,
observer: function (newVal, oldVal) {
if(!newVal && this.data.timer > 0) {
setTimeout(() => { this.setData({ hidden: true})}, this.data.timer);
}
this.setData({ animateCss: newVal ? "my-animate-notify-upout" : "my-animate-notify-downin"});
}
}
},
/**
* 组件的初始数据
*/
data: {
animateCss: 'my-animate-notify-downin'
},
/**
* 组件的方法列表
*/
methods: {
}
})
2)目标页面,引用组件,并通过setData实现toptips显示与否
index.json, 定义组件的tag标签
{
"navigationBarTitleText": "Js模块化,组件化Demo",
"usingComponents": {
"my-toptips": "/components/toptips/toptips"
}
}
3)目标页面,js调用
showTopTips2(e) {
this.setData({
['toptips.text']: "自定义组件方式调用!",
['toptips.hidden']: false
});
}
最终效果图如下: