微信小程序开发踩坑

Posted by Youzi Blog on April 26, 2020

微信小程序开发踩坑

基于微信小程序,使用初始小程序云开发模板进行开发;

记录一些组件、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属性,在文档中inputvalue被定义为输入框的初始值,所以可以不写,但是由于微信的输入值不是双向绑定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和腾讯地图的相关样例,用这个可以做到很多事;

小程序使用map和腾讯地图的例子

  • 定位问题:首先定位问题是和授权相关的,我们首先要取得用户关于位置的授权,目前的话是可以直接调用wx.getLocation函数就会从底部拉起授权询问框;但是呢由于我们需要处理用户拒绝后的情况,所以我给它写成了组件,组件代码就不贴了,给出一张流程图;

位置授权

  • 一般我们直接调用getLocation函数获取的定位坐标都不会特别精确,所以在我们取得位置授权后,会选择继续调用wx.chooseLoation让用户手动选择一下他想要的定位,这可能会造成一些产品上的问题,比如定位了一个很远的位置,假装自己在那个位置,这时候可以用getLocation获取到的坐标,与chooseLocation获取到的坐标,计算坐标差值反向计算差距范围(这样的方法目前还没去查,不过应该是有现成的算法),如果差距过大的话就提示偏离定位太远。