原生组件导致异常样式或布局

scroll-view

1
2
3
4
5
6
7
8
9
10
11
12
<view class='container'>
<scroll-view>
<view class='section'>
</view>

<view class='section'>
</view>
<scroll-view>
<view>
float section
</view>
</view>

1、当整个页面局部需要滚动时候,使用此组件,如果需要通过pageonReachBottom事件触发上啦刷新的话最好不用此组件。当然也要看具体情境了。

2、scroll-view 组件直接子元素设置box-shadow属性不好使。经过试验和验证,发现scroll-view组件有个类似和overflow: hidden样式的设置,且去不掉。所以我又在scroll-view里加了个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
4
this.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
12
globalData:{
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
15
globalData:{
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
3
4
5
6
7
8
9
10
11
12
// 如果想每次进入小程序就从新登录,拉取用户信息
// 只需要在 app.js 同步清除 skey 缓存数据
App({
onLaunch: function (options) {
try {
wx.removeStorageSync('skey')
} catch (e) {
// Do something when catch error
}

},
)}

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
  /**
* 格式化电话号
*
* @param
* tel 电话号码
*/
formatTel(tel) {
let temp = tel.split('')
temp.splice(3, 0, ' ')
temp.splice(8, 0, ' ')
return temp.join('')
}

6、 当无网络连接状态提示

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
3
attached(){
console.log(this.data.dataName)
}

2、样式绑定

1
2
<!-- 组件 -->
<view style='{{style1}} {{style2}}'></view>

1
2
3
4
5
6
7
8
9
10
11
12
13
// js
Component({
properties: {
style1:{
type: String,
value: ''
},
style2:{
type: String,
value: ''
}
}
})
1
2
<!-- 引用 -->
<component head-style='width: 160rpx;height: 160rpx;'></component>

3、绑定class

1
<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
24
this.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
2
3
4
5
6
7
8
9
10
<!-- component 自定义组件dom结构 -->
<view class='video-wrapper' bindtap='onPlay'>
<video src='{{src}}' class='video' id="myVideo"
bindplay='play'
bindpause='pause'
bindended='ended'
bindfullscreenchange='fullscreenchange'>
</video>
<view class='icon'></view>
</view>
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

//js
Component({
properties: {
src: { // 视频地址
type: String,
value: ''
},
},
data: {
},
methods: {
play() {
this.triggerEvent('myPlay', {}) // 触发引用组件 当play时的事件
},
pause() {
this.triggerEvent('myPause', {})
},
ended() {
this.triggerEvent('myEnded', {})
},
/**
// 视频播放
*
*/
onPlay() {
this.videoContext.play()
this.requestFullScreen()
},
/**
* 视频全屏状态改变
*/
fullscreenchange(event) {
const fullScreen = event.detail.fullScreen
if (!fullScreen) {
this.videoContext.pause()
}
},
exitFullScreen(){
this.videoContext.exitFullScreen()
},
requestFullScreen() {
this.videoContext.requestFullScreen()
}
},
ready() {
// 第一个参数必须是 id
// 自定义组件中使用video标签必须加第二个参数 this
this.videoContext = wx.createVideoContext('myVideo',this)
},

})

使用组件

1
2
3
<my-video src='video.src'
bind:myPlay='play'>
</my-video>

1
2
3
4
5
Page({
play () {
console.log('myPlay')
}
})

遇到其他的坑

1、获取分享二维码
这里前端不能直接请求api给的链接,因为小程序请求接口域名限制,只能通过后台请求再返给前端。且原始返回的图片好像是二进制码,需要后台转换成base64格式。如果后台返回字符串没有显示出图片,很可能是少拼接东西,前端/后端需做一下处理

1
'data:image/png;base64,' + res.data.base64

2、去掉button组件的border样式,小程序使用::after实现的

1
2
3
button::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:ifwx: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
2
3
4
5
6
7
8
9
10
11
12
13
Component(
properties:{
propData:{
type: Object,
value: {}
},
},
attached(){
console.log(this.data.propData)
// 如果是头部插入数据,动态生成的组件,在js中获取的数据一直是调用此组件数组的最后一条数据。从而导致传递给其子组件的数据有问题
// 如果没有掉子组件,并且js中没有处理数据后渲染,直接展示数据,渲染是没问题的
}
)

hack 解决办法,利用setData的回调函数

1
2
3
4
5
6
7
8
9
10
insertHead (){
let newArray = [newData, ...this.data.array] // 暂存头部插入后的数据
this.setData({
array: [],// 先置空数组
},() =>{// 设置成功后回调,再把头部插入数据后数组复制给array
this.setData({
array: newArray
})
})
}

源码地址

持续更新中。。。