uniapp踩坑记录

最近公司在用 mPaaS 搞小程序,uniapp 模板那一套,记录一下使用中的一些踩坑点。

说明:

文章中提到的小程序 mini program 统一简称 MP

运行小程序 props 传值,对象方法丢失

uniapp 的 prop 传递的变量为对象时,对象内部含有函数属性,该函数属性会直接被删除。

了解更多,详见

运行 H5 时 v-if、v-for 值更细渲染异常

mPaaS + uniapp 框架下,会复现。

解决方法:

  • 通过条件编译,H5 时用 html5 标签
  • 有 v-if、v-for 的地方直接 div 代替 view,运行小程序时,div 会自动编译为 view,详见

运行小程序 image 标签@error 事件直接替换 src 值,渲染无效

只能通过条件渲染,如下:

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
<template>
<image v-if="!isError" :src="item.img" @load="onLoadImg" @error="onErrorImg" /></image>
<image v-else :src="errorImg" @load="onLoadImg" /></image>
</template>

<script>
export default {
props: {
errorImg: {
type: String,
default: ''
}
},
data() {
return {
isError: false // 图片加载状态,默认错误
}
},
methods: {
onErrorImg() {
this.isError = true;
},
onLoadImg() {
// ...
}
}
}
</script>

小程序无法通过 document 获取 DOM 节点信息

如果有获取元素距离顶部的间距这样一个场景,小程序无法通过 document 来获取相关信息,需要用到uni.createSelectorQuery()。用法以下几点需要注意:

  • 使用 uni.createSelectorQuery() 需要在生命周期 mounted 后进行调用。
  • 默认需要使用到 selectorQuery.in(this) 方法,因为 this,如需公共写法,只能 Vue 的 mixins 混入写法,可挂载到全局。
  • 支付宝小程序不支持 in(component),使用无效果

兼容实现方案:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
getRect(selector, all) {
return new Promise(resolve => {
let query = uni.createSelectorQuery()
// #ifndef MP-ALIPAY
.in(this);
// #endif
query[all ? 'selectAll' : 'select'](selector)
.boundingClientRect(rect => {
if (all & Array.isArray(rect) && rect.length) {
resolve(rect);
}
if(!all && rect) {
resolve(rect);
}
})
.exec();
})
}

更多,详见

顶部搜索下面的吸顶总会有间距

解决方案:吸顶时,外层元素增加padding-top,可以根据需求设置背景色;同时,下方加个占位高度的元素,防止吸顶时,下面的开头部分内容被遮住。

onHide()和 onUnload()触发时机

onHide()触发的场景

  • 导航页 1 => 导航页 2,会触发导航页 1 onHide()
  • ? 导航页 => 子页面,会触导航页 onHide()
  • 子页面 1 => 子页面 2,会触发子页面 1 onHide()

onUnload 触发的场景

  • 子页面 2 返回子页面 1,会触发子页面 2 onUnload()
  • 子页面返回导航页,会触发子页面 onUnload()

注意:

  • 导航页之间的切换不会触发 onUnload()
  • 页面 2 返回到(页面 1 或者导航页)时,页面 2 只会触发 onUnload(),并不会触发 onHide()

页面路由跳转

使用 uni.navigateTo(OBJECT)时,url 可以是相对路径,也可以是绝对路径,注意文件目录层级关系。

v-if、v-show 编译到小程序中的坑

v-if

编译的时候,小程序那边成了 display:none\block 控制,并不是 DOM 的创建和移除。

使用定位会造成盒子错乱,距离尺寸不好把控,在使用 v-if 的时候尽量多套一个盒子去适配好一点。

v-show

编译的时候,小程序那边也成了 display:none\block 控制,但是多了一个 CSS 权重问题,可能会照成控制盒子不生效问题,尽量避免使用。

总结:

在使用自定义组件 tab 选项卡时会有使用 v-if/v-show 的时候需要多套一个盒子,来避免造成 css 的样式在其他端(H5、mini program)不兼容等问题。

图片错误展示占位图兼容 H5/MP

需要通过 import 引入展位图,data 赋值,然后用 v-if 判断展示展位图。

禁止 ios 橡皮筋效果

可以使用页面的 onPageScroll 事件来禁止 iOS 上的橡皮筋效果。

在页面的 onLoad 生命周期函数中,判断当前设备是否是 iOS,并将 disableScroll 属性设置为 true

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
export default {
onLoad() {
// 判断当前设备是否是 iOS
if (/iP(hone|od|ad)/.test(navigator.userAgent)) {
// 禁用 iOS 上的橡皮筋效果
this.disableScroll = true;
}
},
data() {
return {
disableScroll: false,
};
},
};
</script>

在页面的 onUnload 生命周期函数中,将 disableScroll 属性设置为 false

1
2
3
4
5
6
7
8
<script>
export default {
onUnload() {
// 恢复 iOS 上的橡皮筋效果
this.disableScroll = false;
},
};
</script>

在页面中监听 onPageScroll 事件,在滚动时判断是否需要禁用橡皮筋效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<script>
export default {
onPageScroll({ scrollTop }) {
// 判断当前页面是否需要禁用橡皮筋效果
if (this.disableScroll) {
// 如果需要禁用橡皮筋效果,则判断是否已经滚动到顶部
if (scrollTop <= 0) {
uni.pageScrollTo({ scrollTop: 1, duration: 0 });
}
// 判断是否已经滚动到底部
const { windowHeight, scrollHeight } = uni.getSystemInfoSync();
if (scrollTop + windowHeight >= scrollHeight) {
uni.pageScrollTo({
scrollTop: scrollHeight - windowHeight - 1,
duration: 0,
});
}
}
},
};
</script>

这样,在 iOS 上滚动页面时就不会出现橡皮筋效果了。需要注意的是,由于 Uniapp 的渲染机制可能会有不同,上述方案可能不适用于所有情况。