作者:u3xyz,来自授权地址
在做最近的小程序项目时,需要使用到省市县联动选择器。浏览了一下小程组件,发现没有直接提供这种组件,比较相近的picker组件也只支持selector|time|date。
当然,我们可以使用view来模拟,但这样开发量会比较大。再往下看,发现了picker-view + picker-view-column这对好基友,这不就是专门用来实现多列滚动选择的吗?既然找到组件了,接下来就是如何实现了?
不急,我们还是先看一下组件最终效果:
使用类实现小程序组件
一些小程序组件通常会被设计成一个类。用代码表示,大概是下面的样子:
-
//componentXX.js
-
-
class ComponentXX() {
-
-
constructor(opts) {
-
this.$scope = opts.$scope;
-
},
-
-
methodA() {
-
// 使用this.$scope.setData()达到修改ViewModel的能力
-
}
-
}
-
-
export default ComponentXX;
使用时,先引用定义,然后在onLoad时new一个实例,再调用实例相应的方法,如:
-
import ComponentXX from 'path/to/conpoentxx/conpoentxx';
-
-
Page({
-
data: {
-
-
},
-
-
onLoad(opts) {
-
this.componentXX = new ComponentXX({
-
$scope: this,
-
opts: {}
-
});
-
},
-
-
bindTap() {
-
this.componentXX.doSomething();
-
}
-
});
项目里有几个组件,也的确是这样实现的。但这里有几个问题: 1. 使用麻烦。除new之外,每次还得增加一些不必要的方法去调用组件实例相应的方法,比如上面的bindTap方法 2. 因为问题1,所以组件的封装性很差 3. 同页面多组件共存问题,是new多个组件吗?
那么,还有没有更简单,更好的组件封装方式呢?
-
page({
-
onLoad(opts) {},
-
method() {}
-
});
其实我们观察小程序页面启动方法会发现: 1. page函数需要传入一个对象参数,这里参数里面的特定函数onXXX(onLoad,onShow)会在页面的特定生命同期被调用 2. 其它挂在对象参数上的方法或属性,可以通过this访问到。在wxml模板中,也可以直接调用 也就是说,我们扩展对象参数,就可以扩展方法在.wxml模板中调用。基于此,有下面的组件封装方式:
-
import ComponentXX from 'path/to/conpoentxx/conpoentxx';
-
-
Page(extend({
-
data: {
-
-
},
-
-
onLoad(opts) {
-
this.initComponentXXX(opts);
-
},
-
}, ComponentXX));
这种组件封装方式,页面注册参数不需要多写额外的方法。看起来更简洁,封装性也更好。
实战:使用extend方式实现AddressPicker组件
-
import location from 'location.js'; // 数据文件。如果觉得数据文件太大,受小程序1M限制,可以通过cgi返回,然后存于缓存
-
-
const AddressPicker = {
-
/**
-
* 省市县联动初始化个数。注意索引从0开始,例如apNumber = 2,则会有3个联动选择控件
-
* @param apNumber
-
*/
-
initAddressPicker(apNumber = 0) {
-
-
// this 即页面VM,可直接使用this.setData()方法
-
-
this.curApIndex = 0;
-
this.conf = [];
-
this.$address = location;
-
this.inited = false;
-
-
// TODO 因为不能控制省份滚动到某个点,所以不能在初始化时指定某个省市县
-
this.$provinceList = this.getProvinceList();
-
-
while (apNumber-- >= 0) {
-
this.conf.push({
-
index: 0,
-
show: false,
-
prePid: 0,
-
pid: 0,
-
cid: 0,
-
tid: 0,
-
provinceList: this.$provinceList,
-
cityList: this.$address[0].children,
-
townyList: this.$address[0].children[0].children
-
});
-
}
-
-
// init ap VM
-
this.setData({
-
__ap: this.conf
-
});
-
-
setTimeout(() => {
-
this.inited = true;
-
}, 200);
-
},
-
-
getProvinceList() {
-
return this.$address.map(item => {
-
return {
-
label: item.label,
-
value: item.value
-
};
-
});
-
},
-
-
getCityList() {
-
try {
|