微信小程序开发踩坑
基于微信小程序,使用初始小程序云开发模板进行开发;
记录一些组件、API 之类的注意事项;
正篇
不知道怎么分类但是又很坑的点
花括号和引号之间如果有空格,将最终被解析成为字符串,感觉这个应该坑过不少人;
1
2
3
4
5
6
7
8
<!-- bad -->
<view wx:for=" ">
</view>
<!-- 被解析成 -->
<view wx:for="">
</view>
授权相关
授权相关的操作,建议是在需要使用授权接口时,才去向用户发起授权申请,并且要说明理由;除了wx.authorize({scope: 'scope.userInfo'})
,其他都可以直接从底部拉起授权窗口;所以我们这里主要是讲授权获取用户基本信息这个接口;
由于wx.getUserInfo()
这个接口改版了,直接调用这个接口不会拉起授权窗口,改成了需要用户手动点击button
才会从下方弹出授权窗口;下面是一个 demo,和文档里的 demo 差不多;
需要注意的是:我一般喜欢用button
直接显示用户昵称,所以会选择覆盖默认的按钮样式(要注意color && background-color
属性用!important
),且在获取到用户信息后把按钮置为disable
状态;
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
<image src=""></image>
<!-- 最好覆盖button的默认样式,这样不会在UI上显得很奇怪 -->
<button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo" disabled=""></button>
<!-- js -->
<script>
data {
// 用户头像的默认值,一般用于未获取到授权时显示的一个默认灰色的头像
userInfo: {
nickName: '点击登录'
},
avatarUrl: './default.png',
// 标志用户是否登录
logged: false
}
// 在load时check一下是否已经获得了用户授权
onLoad(){
wx.getSetting({
success: res => {
if (res.authSetting['scope.userInfo']) {
// 已经授权,可以直接调用 getUserInfo 获取头像昵称
wx.getUserInfo({
success: res => {
this.setData({
logged: true,
avatarUrl: res.userInfo.avatarUrl,
userInfo: res.userInfo
});
}
});
}
}
});
}
// 获取用户信息
onGetUserInfo(e) {
if (!this.data.logged && e.detail.userInfo) {
this.setData({
logged: true,
avatarUrl: e.detail.userInfo.avatarUrl,
userInfo: e.detail.userInfo
})
} else {
// 如果用户没有授权,可以弹出对话框提示用户需要授权才能使用
}
}
</script>
补充说明:如果使用云开发其实不需要用户进行授权,后台都能获取到openid
等信息;
补充说明 2:如果只想拿到除openid
以外的一些信息,可以直接用open-data
这个组件,直接获取,比写授权方便多了。
全局 style
- 模板项目的全局配置文件
app.json
中有一个sytle: 'v2'
的属性,文档描述微信客户端 7.0 开始,UI 界面进行了大改版。小程序也进行了基础组件的样式升级。app.json 中配置 "style": "v2"可表明启用新版的组件样式
;需要的时候可以把这个去掉,有比较多的样式都会变得不一样。 - 也可以在
app.wxss
文件里,写全局样式加上!important
去覆盖掉。
text
组件
- 内容不要换行,要紧贴标签写内容,因为最开头的换行符会被渲染出来。
1
2
3
4
5
6
7
<!-- bad -->
<text class="" selectable="false" space="false" decode="false">
content
</text>
<!-- good -->
<text class="" selectable="false" space="false" decode="false">content</text>
loading
相关
- 当数据初始化渲染时,可以直接使用 API 控制下拉的
loading
,没必要使用mp-loading
组件。仅限下拉 loading 能满足需求时使用,其他要使用到组件的还是推荐用组件。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<!-- not recommended -->
<mp-loading duration="500" ext-class="loading" type="dot-gray" show="" animated=""></mp-loading>
<view wx:if=""></view>
<!-- recommend -->
<view wx:if=""></view>
<!-- js -->
<script>
onRead() {
wx.startPullDownRefresh();
},
onPullDownRefresh() {
wx.stopPullDownRefresh();
},
</script>
- 另外目前发现了
mp-loading
组件的 bug,设置type: dot-white
时无法正常渲染;还有就是最好设置ext-class
并且给一个宽度,有可能会发生只渲染出一个点的情况。
mp-form
相关
- 组件内部没有
*
符号标注必填项,一般我会用下面的方法自己写。
1
2
3
4
5
6
7
<mp-cell prop="name" show-error>
<view slot="title" class="weui-label">
必填项
<text style="color:red">*</text>
</view>
<input />
</mp-cell>
效果如下图:
input
组件内部没有输入字数统计,写了一个,但是没有写成组件的模式;
html
1
2
3
4
5
6
7
8
9
10
<input
type="text"
data-field="name"
class="weui-input"
placeholder="最多"
bindinput="formInputChange"
value=""
maxlength=""
/>
<view slot="footer" class="ext-footer">/字</view>
css
1
2
3
4
5
.ext-footer {
display: inline-block;
padding-right: 10rpx;
vertical-align: middle;
}
js
1
2
3
4
5
6
if (field === 'name') {
this.setData({
nameLength: val.length,
[`formData.${field}`]: val
});
}
效果如下图:
input
标签一般可以不写value
属性,在文档中input
的value
被定义为输入框的初始值,所以可以不写,但是由于微信的输入值不是双向绑定data
里面的值,所以需要写bindinput
事件函数,来手动改变data
里面的值;另外当你想手动去修改input
框显示值的时候,是需要写value
的
表单校验的一些问题
- 关于
form
的校验,只要写了rules
,那么在输入任何一项时都会触发表单的校验,感觉有点鸡肋,触发频率太高了,所以建议使用防抖函数来规避在输入时一直触发校验函数的问题。
由这个引申的问题,就如果一个表单填写项比较多的话,我们一般会给它分小项去填写,在标签中表现出来的就是用多个mp-cells
,在样式上看起来会比较有层次分明的感觉,但是这不能解决随便输入哪一项都会触发校验函数的问题,如果校验函数很多我感觉会损失些效率,所以我个人感觉如果表单项很多,可以将表单项的几个小类分别写成独立mp-form
,由这些表单小项去自己校验自己,最后再统一进行提交,并且当表单校验出错时,会停止下一个小项的校验;为此我写了个方法。
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
/**
* @desc: 组件外部点击提交时,在组件外手动调用selectComponent来触发这个方法;
* @backup: 为什么要手动返回promise对象呢,因为如果单纯返回validate这个函数,无法给父组件传递参数,所以选择手动封装一层promise
* @return {promise}: 返回promise对象,如果不需要填写则返回null,校验成功调用res,失败调用rej,参数分别是表单信息和失败信息;
* @author: youzi
* @Date: 2020-04-28 20:26:18
*/
submitForm() {
return new Promise((res, rej) => {
if (!this.data.addressChecked) {
res(null);
return null;
}
this.selectComponent('#form').validate((valid, err) => {
valid ? res(this.data.formData) : rej({ errMsg: err[0].message });
});
});
}
}
/**
* @desc: 提交按钮事件,触发各组件的校验函数submitForm,当组件抛出错误时,catch之后调用mp-toptops提示用户,并结束本函数的运行。
* @backup: 逻辑上采用异步函数同步化来写,所有校验函数都会返回promise对象,当前一个函数reject时,就不会继续执行下一个了;值得注意的是,有些组件可以返回为null,表示不需要填写这一项。
* @param {e}
* @author: youzi
* @Date: 2020-04-28 20:49:42
*/
// 总的提交方法,可以采用遍历的形式,循环触发表单的提交方法
async submitForm(e) {
try {
await this.selectComponent('#id')
.submitForm()
.then(res => {
console.warn(res);
if (res !== null) {
this.setData({
['formData.id']: {
...res
}
});
}
});
} catch (err) {
console.error(err);
this.setData({
error: err.errMsg
});
return;
}
console.log('?');
},
- 表单错误提示:目前在文档中看到的示例代码都喜欢用
mp-toptips
组件来提示用户哪里校验出错了。官方代码里面比较好玩的操作;
1
2
<!-- 直接用错误提示来判断组件的显示与不显示 -->
<mp-toptips msg="" type="error" show=""></mp-toptips>
路径相关
- 在
json
文件里,可以直接用/
表示小程序根目录(miniprogram);而在js
文件中应该要使用相对路径来引入文件;目前看到在wxml
文件里可以用/
表示根目录;最好的方法建议是都使用相对路径,目前还没有发现相对路径会出现什么问题
定位(地图组件)相关
提示:只是定位的话,不需要用到map
组件;可以看看小程序官方提供的例子,提供了包括使用map
和腾讯地图的相关样例,用这个可以做到很多事;
- 定位问题:首先定位问题是和授权相关的,我们首先要取得用户关于位置的授权,目前的话是可以直接调用
wx.getLocation
函数就会从底部拉起授权询问框;但是呢由于我们需要处理用户拒绝后的情况,所以我给它写成了组件,组件代码就不贴了,给出一张流程图;
- 一般我们直接调用
getLocation
函数获取的定位坐标都不会特别精确,所以在我们取得位置授权后,会选择继续调用wx.chooseLoation
让用户手动选择一下他想要的定位,这可能会造成一些产品上的问题,比如定位了一个很远的位置,假装自己在那个位置,这时候可以用getLocation
获取到的坐标,与chooseLocation
获取到的坐标,计算坐标差值反向计算差距范围(这样的方法目前还没去查,不过应该是有现成的算法),如果差距过大的话就提示偏离定位太远。