微信小程序已经推出一段时间了,官方也提供很多组件,但是有些业务场景,官方组件难免有些捉襟见肘,这时候,就需要自己开发一个自定义组件了。但是微信小程序其实是做了很多限制的,本文记录了开发(踩坑)一个公共登录弹出框组件的过程,仅供参考。
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);
|
到此,自定义的组件就写好啦:
PS:由于用到了ES6特性,需要在小程序调试工具中勾选“开启ES6转ES5”。
3. 写在最后
小程序官方还是限制太多,我并不看好。但是对于初创公司还是有点意义的,这时候遇到自定义组件的需求,只好带着镣铐跳舞。本文只是给出一个写一个微信小程序自定义组件的思路,所谓条条大路通罗马,肯定还有更合适的方案,能力有限,仅供参考。
原文:https://segmentfault.com/p/1210000007842233