微信小程序中遇到的坑和一些小技巧
原生组件导致异常样式或布局
scroll-view
| 1 | <view class='container'> | 
  1、当整个页面局部需要滚动时候,使用此组件,如果需要通过pageonReachBottom事件触发上啦刷新的话最好不用此组件。当然也要看具体情境了。
  2、scroll-view 组件直接子元素设置box-shadow属性不好使。经过试验和验证,发现scroll-view组件有个类似和overflow: hidden样式的设置,且去不掉。所以我又在scroll-view里加了个view标签
  3、textarea 层级过高。目前微信还没有解决这个bug,自定义弹层如果内容比较丰富是不能使用cover-view标签来达到覆盖在原生组件之上的文本视图,因为其只支持嵌套 cover-view、cover-image、 button。所以只能hack实现。textarea外用view包裹,设个固定高度,当弹层展示就隐藏textarea
  1
2
3<view style="height:400rpx">
<textarea v-if="show" style="height:400" />
</view>
技巧总结
Page
  1、页面中声明data数据,如果有些数据页面渲染中没有直接用到,只是在逻辑代码中作为中间量,完全可以直接声明给this 对象。这样就省去调用this.setData()方法
  3、获取options.scene的值需要 decodeURIComponent(options.scene)下
app
1、下面这句是判断 userInfoReadyCallback 是否定义了,若没定义,说明其在Page.onLoad 定义userInfoReadCallback 之前运行的,说明app.globalInfo.userInfo已经包含了用户登录的信息了。
 若定义了,说明在Page.onLoad比该语句返回的success结果之前已经运行了。此时的app.globalInfo.userInfo的值是空的,所以还需要再重新对其进行赋值。
  1
2
3
4this.globalData.userInfo = res.data.data
if (this.userInfoReadyCallback) {
  this.userInfoReadyCallback(res.data.data)
}
  2、当定义初始的一个全局变量是Boolean值并为false或者null时,只要没有给此变量再次赋值,调用此全局变量失败
  1
2
3
4
5
6
7
8
9
10
11
12globalData:{
  prop1: null,
  prop2: false
}
// 调用
if(app.globalData.prop1){
  console.log('success')
} else {
  app.prop1ReadyCallback = () => {
    console.log('success')
  }
}
  如果不想初始化,就可以调用全局变量,可以把全局变量给以字符串初始值,就可以正常调用了
  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15globalData:{
  prop2: 'false'
}
// 调用
if(app.globalData.prop1){
  if(!!app.globalData.prop1){
  }
} else {
  app.prop1ReadyCallback = () => {
    if(!!app.globalData.prop1){
    
    }
  }
}
自定义组件
1、 调用自定义组件在父组件中加类名,写的样式除了可继承属性外,不能作用自定组将
2、 自定义组件中使用`catchtap`绑定事件,也可以取消事件冒泡,即不调用父组件中的事件
3、 `hidden` 直接条件渲染组件标签无效果,只能使用`view`包裹自定义组件的标签,并把`hidden`放在`view`上
方法封装
  1、封装登录reLogin方法
  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  /**
 * 重新登录
 * params
 *       callback    登录成功后的回调函数
 */
reLogin({ callback = () => { } } = {}) {
  // app.globalData.loging = true
  wx.showLoading({
    title: '加载中'
  })
  wx.login({
    success: res => {
      // 发送 res.code 到后台换取 openId, sessionKey, unionId
      const code = res.code
      wx.getUserInfo({
        success: res => {
          // 可以将 res 发送给后台解码出 unionId
          const userInfo = res.userInfo
          const encryptedData = res.encryptedData
          const iv = res.iv
          // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
          // 所以此处加入 callback 以防止这种情况
          wx.request({
            url: 'https:****login',
            header: {
              'content-type': 'application/json', // 默认值
              'x-wx-code': code,
              'x-wx-encrypted-data': encryptedData,
              'x-wx-iv': iv,
              'x-wx-skey': ''
            },
            success: (res) => {
              wx.hideLoading()
              wx.setStorage({
                key: "skey",
                data: res.data.data.SKey,
                success: () => {
                  callback()
                },
                fail: () => {
                  console.log('set fail')
                }
              })
              wx.setStorage({
                key: "userInfo",
                data: res.data.data,
                success: () => {
                },
                fail: () => {
                  console.log('set fail')
                }
              })
            },
            fail: function (err) {
              wx.hideLoading()
              wx.showToast({
                title: '貌似断网了,请检查您的网络~',
                icon: 'none',
                duration: 2000
              })
            },
            complete: () => {
            }
          })
        }
      })
    }
  })
},
| 1 | // 如果想每次进入小程序就从新登录,拉取用户信息 | 
  2、封装wx.request为fetch 异步请求接口
  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
84
85
86
87
88
89
90
91
92  module.exports = {
    /**
   * 请求接口的公共方法
   *
   * @params   
   *           app      app 对象
   *           url      接口地址
   *           header   请求头
   *           data     参数
   *           method   请求方法
   *           success  成功回调函数
   *           fail     失败回调
   *           complete 完成回调
   */
fetch(...rest) {
    // 定义单个请求方法
    const request = (skey, index,{ url = '', method = 'GET', header = {}, data = '', success = () => { }, fail = () => { }, complete = () => { }, loading = true } = {}) => {
      // 是否显示loading 
      if (loading) {
        wx.showLoading({
          title: '加载中',
          mask: true,
        })
      }
      wx.request({
        url: fetchDomain + url,
        header: {
          ...header,
          'x-wx-skey': skey
        },
        method: method,
        data: data,
        success: res => {
          const data = res.data
          wx.hideLoading()
          switch (data.code) {
            case 0:
              success(data)
              break
            case -2: // 会话过期
              if(index == 0){// 如果是异步请求多个接口,只执行一次回调
                console.log(index)
                this.reLogin({ callback: () => { this.fetch(...rest) } })
              }
            default:
              wx.showToast({
                title: data.message,
                icon: 'none',
                duration: 2000
              })
          }
        },
        fail: res => {
          wx.hideLoading()
          wx.showToast({
            title: '貌似断网了,请检查您的网络~',
            icon: 'none',
            duration: 2000
          })
          fail(res)
        },
        complete: res => {
          complete(res)
        }
      })
    }
    // 存在skey值采取请求接口
    const checkSkey = () => wx.getStorage({
      key: 'skey',
      success: res => {
        rest.forEach((value,index) => {
          request(res.data, index, value)
        })
      },
      fail: () => {
        console.log('fail')
        this.reLogin({ callback: () => { this.fetch(...rest) } })
      }
    })
    // 是否有网络连接
    this.isConnected({
      callback: () => {
        checkSkey()
      }
    })
  },
这里说一下,为什么封装为可以异步请求接口,因为有时候页面初始渲染需要请求至少2个以上的接口,且如果接口传入参数中用到了app globalData数据,并把判断globalData数据是否可用封装在了fetch方法里,就会导致只有最后一个请求的接口,请求成功。也可以避免会话过期,重复reLogin导致循环会话过期reLogin的问题
3、获取scene中参数方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
 * 获取scene 中的参数
 * @param   
 *          scene  onLoad.options.scene
 */
getSceneParams(scene) {
  if (!scene.includes('&')) {
    return false
  }
  let arry = scene.split('&')
  let query = {}
  arry.forEach(item => {
    const temp = item.split('=')
    query[temp[0]] = temp[1] || ''
  })
  return query
}
4、判断app globalData 数据是否可用,并执行相应的回调函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
 * 判断是否能够使用app全局变量
 * 
 * @params
 *            key             全局变量
 *            existCallback   全局变量存在  callback 之前回调函数
 *            inExistCallback 全局变量不存在 callback 之前回调函数
 *            callback        共同回调函数
 *            
 */
useGlobalData({ key = '', callback = () => { }, existCallback = () => { }, inExistCallback = () => { } } = {}) {
  const keyReadyCallback = `${key}ReadyCallback`
  if (app.globalData[key]) {
    existCallback(app.globalData[key])
    callback(app.globalData[key])
  } else {
    app[keyReadyCallback] = () => {
      inExistCallback(app.globalData[key])
      callback(app.globalData[key])
    }
  }
}
5、 当无网络连接状态提示
  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  /**
 * 无网络连接状态提示
 */
isConnected({ callback = () => { } } = {}) {
  wx.getNetworkType({
    success: function (res) {
      // 返回网络类型, 有效值:
      // wifi/2g/3g/4g/unknown(Android下不常见的网络类型)/none(无网络)
      var networkType = res.networkType
      if (networkType == 'none') {
        wx.hideLoading()
        wx.showToast({
          title: '当前无网络连接,请查看您的网络设置~',
          icon: 'none',
          duration: 2000
        })
      } else {
        callback()
      }
    },
    fail: () => {
    }
  })
}
自定义组件
  1、获取对外属性properties的值,在created周期获取不到,需要在attached周期函数中获取
  1
2
3attached(){
  console.log(this.data.dataName)
}
2、样式绑定1
2<!-- 组件 -->
<view style='{{style1}} {{style2}}'></view>
| 1 | // js | 
| 1 | <!-- 引用 --> | 
3、绑定class1
<view class='class1  class2 {{triggle ? "classTriggle" : ""}}'></view>
4、setData一个对象中一个属性的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24this.setData({
  ['obj.propName']: 'new value'
})
const name = 'propName'
// 也可以是表达式的形式
this.setData({
  [`obj.${name}`]: 'new value'
})
Page({
  data:{
    userInfo:{
      nickName: '',
      age: 16
    }
  },
  setObjectProp () {
    this.setData({
      ['userInfo.nickName']: 'new name'
    })
  }
})
自定义封装video组件
| 1 | <!-- component 自定义组件dom结构 --> | 
| 1 | 
 | 
使用组件1
2
3<my-video src='video.src'
  bind:myPlay='play'>
</my-video>
| 1 | Page({ | 
遇到其他的坑
  1、获取分享二维码
  这里前端不能直接请求api给的链接,因为小程序请求接口域名限制,只能通过后台请求再返给前端。且原始返回的图片好像是二进制码,需要后台转换成base64格式。如果后台返回字符串没有显示出图片,很可能是少拼接东西,前端/后端需做一下处理
  1
'data:image/png;base64,' + res.data.base64
  2、去掉button组件的border样式,小程序使用::after实现的
  1
2
3button::after{
  display: none;
}
bug
 1、textarea show-confirm-bar='true' 无效。
 1
2
3<textarea show-confirm-bar=''></textarea>
// 或者
<textarea show-confirm-bar='{{falseVar}}'></textarea>
 2、wx:if和wx:else 搭配
 1
2
3
4
5
6
7
8
9<view wx:if='{{false}}' wx:for='{{}}'></view>
<view wx:else></view>
<!-- 此时就会报错 -->
<!-- 改为 -->
<block wx:if='{{false}}'>
  <view wx:for></view>
</block>
<view wx:else></view>
 3、request请求接口安卓手机有时候会返回String而不是json,需要通过trim()去掉bom头
 1
2
3
4
5
6
7//  加个判断
let data = null
if(typeof res.data === 'string'){
  data = res.data.trim()
}eles{
  data = res.data
}
 4、页面循环一个数组动态头部插入自定义组件,且自定义组件(父组件)又引用了另一个自定义组件(子组件),子组件用到了父组件传递的数据,从而导致子组件渲染数据有问题,如下1
2
3
4<!-- page -->
<block wx:for='{{array}}'>
  <component prop-data='{{item}}'></component>
</block>
父组件1
2
3<view>
  <component-sub prop-data-sub='{{propData.content}}'></component-sub>
</view>
| 1 | Component( | 
hack 解决办法,利用setData的回调函数1
2
3
4
5
6
7
8
9
10insertHead (){
  let newArray = [newData, ...this.data.array] // 暂存头部插入后的数据
  this.setData({
    array: [],// 先置空数组
  },() =>{// 设置成功后回调,再把头部插入数据后数组复制给array
      this.setData({
        array: newArray
      })
  })
}
持续更新中。。height:400rpx”>
  
  2、当定义初始的一个全局变量是Boolean值并为false或者null时,只要没有给此变量再次赋值,调用此全局变量失败
  1
2
3
4
5
6
7
8
9
10
11
12globalData:{
  prop1: null,
  prop2: false
}
// 调用
if(app.globalData.prop1){
  console.log('success')
} else {
  app.prop1ReadyCallback = () => {
    console.log('success')
  }
}
  如果不想初始化,就可以调用全局变量,可以把全局变量给以字符串初始值,就可以正常调用了
  1
2
3
4
5
6
7
8
9
10
11
12
13
14
15globalData:{
  prop2: 'false'
}
// 调用
if(app.globalData.prop1){
  if(!!app.globalData.prop1){
  }
} else {
  app.prop1ReadyCallback = () => {
    if(!!app.globalData.prop1){
    
    }
  }
}
自定义组件
1、 调用自定义组件在父组件中加类名,写的样式除了可继承属性外,不能作用自定组将
2、 自定义组件中使用`catchtap`绑定事件,也可以取消事件冒泡,即不调用父组件中的事件
3、 `hidden` 直接条件渲染组件标签无效果,只能使用`view`包裹自定义组件的标签,并把`hidden`放在`view`上
方法封装
  1、封装登录reLogin方法
  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  /**
 * 重新登录
 * params
 *       callback    登录成功后的回调函数
 */
reLogin({ callback = () => { } } = {}) {
  // app.globalData.loging = true
  wx.showLoading({
    title: '加载中'
  })
  wx.login({
    success: res => {
      // 发送 res.code 到后台换取 openId, sessionKey, unionId
      const code = res.code
      wx.getUserInfo({
        success: res => {
          // 可以将 res 发送给后台解码出 unionId
          const userInfo = res.userInfo
          const encryptedData = res.encryptedData
          const iv = res.iv
          // 由于 getUserInfo 是网络请求,可能会在 Page.onLoad 之后才返回
          // 所以此处加入 callback 以防止这种情况
          wx.request({
            url: 'https:****login',
            header: {
              'content-type': 'application/json', // 默认值
              'x-wx-code': code,
              'x-wx-encrypted-data': encryptedData,
              'x-wx-iv': iv,
              'x-wx-skey': ''
            },
            success: (res) => {
              wx.hideLoading()
              wx.setStorage({
                key: "skey",
                data: res.data.data.SKey,
                success: () => {
                  callback()
                },
                fail: () => {
                  console.log('set fail')
                }
              })
              wx.setStorage({
                key: "userInfo",
                data: res.data.data,
                success: () => {
                },
                fail: () => {
                  console.log('set fail')
                }
              })
            },
            fail: function (err) {
              wx.hideLoading()
              wx.showToast({
                title: '貌似断网了,请检查您的网络~',
                icon: 'none',
                duration: 2000
              })
            },
            complete: () => {
            }
          })
        }
      })
    }
  })
},
| 1 | // 如果想每次进入小程序就从新登录,拉取用户信息 | 
  2、封装wx.request为fetch 异步请求接口
  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
84
85
86
87
88
89
90
91
92  module.exports = {
    /**
   * 请求接口的公共方法
   *
   * @params   
   *           app      app 对象
   *           url      接口地址
   *           header   请求头
   *           data     参数
   *           method   请求方法
   *           success  成功回调函数
   *           fail     失败回调
   *           complete 完成回调
   */
fetch(...rest) {
    // 定义单个请求方法
    const request = (skey, index,{ url = '', method = 'GET', header = {}, data = '', success = () => { }, fail = () => { }, complete = () => { }, loading = true } = {}) => {
      // 是否显示loading 
      if (loading) {
        wx.showLoading({
          title: '加载中',
          mask: true,
        })
      }
      wx.request({
        url: fetchDomain + url,
        header: {
          ...header,
          'x-wx-skey': skey
        },
        method: method,
        data: data,
        success: res => {
          const data = res.data
          wx.hideLoading()
          switch (data.code) {
            case 0:
              success(data)
              break
            case -2: // 会话过期
              if(index == 0){// 如果是异步请求多个接口,只执行一次回调
                console.log(index)
                this.reLogin({ callback: () => { this.fetch(...rest) } })
              }
            default:
              wx.showToast({
                title: data.message,
                icon: 'none',
                duration: 2000
              })
          }
        },
        fail: res => {
          wx.hideLoading()
          wx.showToast({
            title: '貌似断网了,请检查您的网络~',
            icon: 'none',
            duration: 2000
          })
          fail(res)
        },
        complete: res => {
          complete(res)
        }
      })
    }
    // 存在skey值采取请求接口
    const checkSkey = () => wx.getStorage({
      key: 'skey',
      success: res => {
        rest.forEach((value,index) => {
          request(res.data, index, value)
        })
      },
      fail: () => {
        console.log('fail')
        this.reLogin({ callback: () => { this.fetch(...rest) } })
      }
    })
    // 是否有网络连接
    this.isConnected({
      callback: () => {
        checkSkey()
      }
    })
  },
这里说一下,为什么封装为可以异步请求接口,因为有时候页面初始渲染需要请求至少2个以上的接口,且如果接口传入参数中用到了app globalData数据,并把判断globalData数据是否可用封装在了fetch方法里,就会导致只有最后一个请求的接口,请求成功。也可以避免会话过期,重复reLogin导致循环会话过期reLogin的问题
3、获取scene中参数方法1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17/**
 * 获取scene 中的参数
 * @param   
 *          scene  onLoad.options.scene
 */
getSceneParams(scene) {
  if (!scene.includes('&')) {
    return false
  }
  let arry = scene.split('&')
  let query = {}
  arry.forEach(item => {
    const temp = item.split('=')
    query[temp[0]] = temp[1] || ''
  })
  return query
}
4、判断app globalData 数据是否可用,并执行相应的回调函数1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22/**
 * 判断是否能够使用app全局变量
 * 
 * @params
 *            key             全局变量
 *            existCallback   全局变量存在  callback 之前回调函数
 *            inExistCallback 全局变量不存在 callback 之前回调函数
 *            callback        共同回调函数
 *            
 */
useGlobalData({ key = '', callback = () => { }, existCallback = () => { }, inExistCallback = () => { } } = {}) {
  const keyReadyCallback = `${key}ReadyCallback`
  if (app.globalData[key]) {
    existCallback(app.globalData[key])
    callback(app.globalData[key])
  } else {
    app[keyReadyCallback] = () => {
      inExistCallback(app.globalData[key])
      callback(app.globalData[key])
    }
  }
}
5、 当无网络连接状态提示
  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  /**
 * 无网络连接状态提示
 */
isConnected({ callback = () => { } } = {}) {
  wx.getNetworkType({
    success: function (res) {
      // 返回网络类型, 有效值:
      // wifi/2g/3g/4g/unknown(Android下不常见的网络类型)/none(无网络)
      var networkType = res.networkType
      if (networkType == 'none') {
        wx.hideLoading()
        wx.showToast({
          title: '当前无网络连接,请查看您的网络设置~',
          icon: 'none',
          duration: 2000
        })
      } else {
        callback()
      }
    },
    fail: () => {
    }
  })
}
自定义组件
  1、获取对外属性properties的值,在created周期获取不到,需要在attached周期函数中获取
  1
2
3attached(){
  console.log(this.data.dataName)
}
2、样式绑定1
2<!-- 组件 -->
<view style='{{style1}} {{style2}}'></view>
| 1 | // js | 
| 1 | <!-- 引用 --> | 
3、绑定class1
<view class='class1  class2 {{triggle ? "classTriggle" : ""}}'></view>
4、setData一个对象中一个属性的值1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24this.setData({
  ['obj.propName']: 'new value'
})
const name = 'propName'
// 也可以是表达式的形式
this.setData({
  [`obj.${name}`]: 'new value'
})
Page({
  data:{
    userInfo:{
      nickName: '',
      age: 16
    }
  },
  setObjectProp () {
    this.setData({
      ['userInfo.nickName']: 'new name'
    })
  }
})
自定义封装video组件
| 1 | <!-- component 自定义组件dom结构 --> | 
| 1 | 
 | 
使用组件1
2
3<my-video src='video.src'
  bind:myPlay='play'>
</my-video>
| 1 | Page({ | 
遇到其他的坑
  1、获取分享二维码
  这里前端不能直接请求api给的链接,因为小程序请求接口域名限制,只能通过后台请求再返给前端。且原始返回的图片好像是二进制码,需要后台转换成base64格式。如果后台返回字符串没有显示出图片,很可能是少拼接东西,前端/后端需做一下处理
  1
'data:image/png;base64,' + res.data.base64
  2、去掉button组件的border样式,小程序使用::after实现的
  1
2
3button::after{
  display: none;
}
bug
 1、textarea show-confirm-bar='true' 无效。
 1
2
3<textarea show-confirm-bar=''></textarea>
// 或者
<textarea show-confirm-bar='{{falseVar}}'></textarea>
 2、wx:if和wx:else 搭配
 1
2
3
4
5
6
7
8
9<view wx:if='{{false}}' wx:for='{{}}'></view>
<view wx:else></view>
<!-- 此时就会报错 -->
<!-- 改为 -->
<block wx:if='{{false}}'>
  <view wx:for></view>
</block>
<view wx:else></view>
 3、request请求接口安卓手机有时候会返回String而不是json,需要通过trim()去掉bom头
 1
2
3
4
5
6
7//  加个判断
let data = null
if(typeof res.data === 'string'){
  data = res.data.trim()
}eles{
  data = res.data
}
 4、页面循环一个数组动态头部插入自定义组件,且自定义组件(父组件)又引用了另一个自定义组件(子组件),子组件用到了父组件传递的数据,从而导致子组件渲染数据有问题,如下1
2
3
4<!-- page -->
<block wx:for='{{array}}'>
  <component prop-data='{{item}}'></component>
</block>
父组件1
2
3<view>
  <component-sub prop-data-sub='{{propData.content}}'></component-sub>
</view>
| 1 | Component( | 
hack 解决办法,利用setData的回调函数1
2
3
4
5
6
7
8
9
10insertHead (){
  let newArray = [newData, ...this.data.array] // 暂存头部插入后的数据
  this.setData({
    array: [],// 先置空数组
  },() =>{// 设置成功后回调,再把头部插入数据后数组复制给array
      this.setData({
        array: newArray
      })
  })
}
持续更新中。。。
 
        
             
                         
                         
                        