写一个微信小程序自定义公共组件

微信小程序已经推出一段时间了,官方也提供很多组件,但是有些业务场景,官方组件难免有些捉襟见肘,这时候,就需要自己开发一个自定义组件了。但是微信小程序其实是做了很多限制的,本文记录了开发(踩坑)一个公共登录弹出框组件的过程,仅供参考。

1. 一个组件的组成部分

一个微信小程序有四中格式的文件*.wxml*.wxss*.js*.json*.json其实是小程序和页面的配置文件,开发组件的时候,就不需要这个了。我这里开发一个登陆弹出框组件,我把组件放在component文件夹下:

 
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
     
Wechat-APP /
├─app.js
├─app.json
├─app.wxss
├─component/
│ └─login-pannel /
│ ├─login-pannel.js
│ ├─login-pannel.wxml
│ └─login-pannel.wxss
├─image/
├─pages /
│ ├─index/
│ ├─merchant-detail /
│ └─merchant-list/
└─utils/

可见,组件也是由三个部分组成。

1.1. wxml

首先新建组件的模版,/component/login-pannel/login-pannel.wxml,同时,给定义的模版一个唯一标识<template name="loginPannel"></template>,代码如下:

 
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
 
     
<template name="loginPannel">
<view class="login-pannel" hidden="{{ isHide }}" >
<view catchtap="__lgpanel_close" class="close-btn"> </view>
<view class="form-area">
<view class="login-inline phone {{ phoneClass }}">
<input placeholder="请填写手机号码" maxlength="11" type="number" bindinput="__lgpanel_phoneInput" bindfocus="__lgpanel_phoneFocus" bindblur="__lgpanel_phoneBlur" />
<text catchtap="__lgpanel_sendCode" class="verify-btn">{{ verifyText }} </text>
</view>
<view class="login-inline vcode {{ vcodeClass }}">
<input placeholder="请填写验证码" maxlength="4" type="number" bindinput="__lgpanel_vcodeInput" bindfocus="__lgpanel_vcodeFocus" bindblur="__lgpanel_vcodeBlur" />
</view>
</view>
<text catchtap="__lgpanel_login" class="login-btn">注册/登录 </text>
</view>
</template>

官方提供了模版的概念,同时又给出了两种引用方式:import 和 include。include 方式是将你的模版原封不动的“粘贴”到引用的地方,而我们这里开发的是一个交互组件,势必会碰到数据绑定和事件绑定,所以我们最终引用的时候,使用 import 的形式来使用组件。

1.2. wxss

定义组件的样式,/component/login-pannel/login-pannel.wxss。样式没什么好说的,这里略过。

1.3. js

首先定义组件的构造函数,构造函数主要做两件事:

  • 把组件的数据“注入”到页面的data对象中;
  • 把组件的事件“合并”到页面对象上。

小程序文档中,关于数据和事件,是这么描述的:

这样,想实现模版的数据绑定,只能想办法把组件上的数据“追加”到页面的 data 对象上去;同理,想实现组件的事件绑定,则需要把组件的事件也“传给”页面函数Page。同时,为了防止影响到其它组件,还要给数据和事件都限定了“命名空间”:

 
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
 
     
let _compData = {
'__lgpanel__.isHide': true,
'__lgpanel__.phoneNum': '',
'__lgpanel__.verifyCode': '',
'__lgpanel__.verifyText': '获取验证码',
'__lgpanel__.phoneClass': '',
'__lgpanel__.vcodeClass': ''
}
let _compEvent = {
__lgpanel_timer: null,
__lgpanel_countDown: 60,
__lgpanel_close: function () {
clearInterval( this.__lgpanel_timer)
this.__lgpanel_countDown = 60
this.setData(_compData)
},
__lgpanel_sendCode: function () {
if ( this.__lgpanel_countDown < 60) {
return
}
this.setData({
'__lgpanel__.verifyText': this.__lgpanel_countDown + 's'
})
this.__lgpanel_timer = setInterval( () => {
this.__lgpanel_countDown--
if ( this.__lgpanel_countDown <= 0) {
clearInterval( this.__lgpanel_timer)
this.__lgpanel_countDown = 60
this.setData({
'__lgpanel__.verifyText': '重新获取'
})
return
}
this.setData({
'__lgpanel__.verifyText': this.__lgpanel_countDown + 's'
})
}, 1000)
typeof this.loginPannel._configs.sendCode == "function" &&
this.loginPannel._configs.sendCode()
}
}
// 小程序最新版把原型链干掉了。。。换种写法
let loginPannel = {
show: function(data) {
this.__page.setData({ '__lgpanel__.isHide': false})
if (data) {
Object.assign( this._configs, data)
}
}
}
function LoginPannel () {
// 定义组件的一些回调
this._configs = {
sendCode: null,
closeCode: null,
login: null
}
// 拿到当前页面对象
let pages = getCurrentPages()
let curPage = pages[pages.length - 1]
// 把组件的事件“合并到”页面对象上
Object.assign(curPage, _compEvent)
this.__page = curPage
Object.assign(curPage, loginPannel) // 小程序最新版把原型链干掉了。。。换种写法
// 附加到page上,方便访问
curPage.loginPannel = this
// 把组件的数据“注入”到页面的data对象中
curPage.setData(_compData)
return this
}

同时,在组件的构造函数的原型链上追加“启动”函数,(微信小程序最新版把原型链干掉了。。。)在构造函数中,把启动方法合并到LoginPannel对象上去,同时把LoginPannel暴露出去:

 
     
1
2
3
4
5
6
7
8
9
10
11
 
     
/* LoginPannel.prototype.show = function(data) {
this.__page.setData({ '__lgpanel__.isHide': false})
if (data) {
Object.assign(this._configs, data)
}
} */
module.exports = {
LoginPannel
}

到这里,一个组件就写好了。

2. 使用自定义组件

由于微信小程序限制太多,使用组件的时候,需要引用三个文件

2.1. 引用 wxml

在要使用组件的页面的wxml文件中引用组件,同时使用模版,传入担任“命名空间”的对象:

 
     
1
2
 
     
<import src="../../component/login-pannel/login-pannel.wxml"/>
<template is="loginPannel" data="{{ ...__lgpanel__ }}"/>

2.2. 引用 wxss

如果你的组件需要在多个页面中使用,那么在app.wxss中引入一次组件样式即可;如果只需要在摸个特定的页面使用,只需在该页面的样式文件中引入组件样式。

 
     
1
 
     
@ import "./component/login-pannel/login-pannel.wxss";

2.3. 引用 js

如果你的组件需要在多个页面中使用,你需要在app.js中引用,同时传给App()

 
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 
     
import { LoginPannel } from './component/login-pannel/login-pannel'
App({
LoginPannel,
_getUserInfo: function (cb) {
var that = this
if ( this.globalData.userInfo) {
typeof cb == "function" &&
cb( this.globalData.userInfo)
} else {
//调用登录接口
wx.login({
success : function () {
wx.getUserInfo({
success : function (res) {
that.globalData.userInfo = res.userInfo
typeof cb == "function" &&
cb(that.globalData.userInfo)
}
})
}
})
}
}
})

然后,在使用的页面的生命周期函数onLoad中“注册”该组件:

 
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
 
     
onLoad: function () {
// 调用应用实例的方法获取全局数据
let app = getApp()
// “注册”组件
new app.LoginPannel()
app._getUserInfo( (userInfo) => {
//更新数据
this.setData({
userInfo:userInfo
})
})
}

然后,在这个页面中就可以愉快的使用组件啦:

 
     
1
2
3
4
5
6
7
8
 
     
openLoginPannel: function() {
this.loginPannel.show({
sendCode: function () {
console.log( 'index - ', 'sendCode')
},
login: this.onLogin
})
}

如果你只是要在特定的页面使用组件,只要在当前页面引用组件即可:

 
     
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 
     
import { LoginPannel } from '../../component/login-pannel/login-pannel'
let pageData = {
LoginPannel,
data: {
motto: '打开登录框',
userInfo: {}
},
openLoginPannel: function() {
this.loginPannel.show({
sendCode: function () {
console.log( 'index - ', 'sendCode')
}
})
},
onLoad: function () {
new this.LoginPannel()
}
}
Page(pageData);

到此,自定义的组件就写好啦:

image

PS:由于用到了ES6特性,需要在小程序调试工具中勾选“开启ES6转ES5”。

3. 写在最后

小程序官方还是限制太多,我并不看好。但是对于初创公司还是有点意义的,这时候遇到自定义组件的需求,只好带着镣铐跳舞。本文只是给出一个写一个微信小程序自定义组件的思路,所谓条条大路通罗马,肯定还有更合适的方案,能力有限,仅供参考。

原文:https://segmentfault.com/p/1210000007842233

猜你喜欢

转载自blog.csdn.net/weixin_36065510/article/details/62032179