UniAPP 踩坑记录
nav-bar
系统自带的导航栏只能更改字体颜色, 无法更改字体大小或其他字体样式, 如确有需要, 可使用第三方 ui 组件库 nav-bar 组件
页面背景色
pages.json 全局配置:
{
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "uni-app",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
}
}局部页面更改背景色:
<style>
page {
background-color: red;
}
</style>Stylelint 标签识别问题
Stylelint 配置以忽略 page标签识别问题:
module.exports = {
extends: ['stylelint-config-standard', 'stylelint-config-standard-scss', 'stylelint-config-recommended-vue/scss'],
rules: {
// 处理小程序标签 unknown 问题, 如 page 标签
'selector-type-no-unknown': [
true,
{
ignoreTypes: ['page']
}
]
}
};样式穿透
TIP
小程序默认只能页面穿透到组件, 而不能组件穿透到组件
Vue2 项目可做如下配置:
<script lang="ts">
export default {
options: {
// 默认值为 apply-shared
styleIsolation: 'shared'
}
};
</script>Vue3 在组件内除 <script setup> 外再新增一个 <script> 标签, 注意两个 script 标签 lang 属性值须一致, 否则编译报错。
<script lang="ts">
export default {
options: {
styleIsolation: 'shared'
}
};
</script>
<script setup lang="ts"></script>不支持的 API
浏览器
document/window/localstorage 等浏览器特有 API 仅在 H5 平台适用, APP-PLUS 和 MP-WEIXIN 平台均不支持
CSS
Vue
在 (微信) 小程序上不支持以下 API:
keep-alive缓存组件component :is动态组件- 不支持自定义指令
directives - 不支持自定义过滤器
filters(仅Vue 2支持,Vue 3已移除过滤器) - 不支持内置指令
v-html/v-once transition/transition-group- 小程序上不支持
jsx/tsx,H5和Web平台支持, 但须安装相应插件 - 不支持
Teleport、Suspense、defineModel等较新的API
H5 scoped
uniapp 在 H5 平台上会自动开启 scoped 以样式隔离, 可使用以下方式规避:
<style noscoped lang="scss"></style>配置 easycom
{
"easycom": {
// uni-ui 引入, 详见 https://github.com/dcloudio/uni-app/issues/3016
// vue-cli vue.config.js transpileDependencies:['@dcloudio/uni-ui']
// vite 配置 autoscan 为 true
// 否则 h5 控制台 uni-ui 组件报错
// Failed to resolve component: uni-fab
"autoscan": true,
// 注意一定要放在custom里,否则无效,https://ask.dcloud.net.cn/question/131175
"custom": {
// uview-plus
"^u--(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^up-(.*)": "uview-plus/components/u-$1/u-$1.vue",
"^u-([^-].*)": "uview-plus/components/u-$1/u-$1.vue",
// uni-ui https://github.com/dcloudio/uni-ui
"^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue",
// lh
"^lh-(.*)": "@subComponents/lh-$1.vue"
}
}
}条件编译
- 条件编译处理多端差异
#ifdef PLATFORM表示if defined in PLATFORM#ifndef PLATFORM表示if not defined in PLATFORM#ifdef PLATFORM_A || PLATFORM_B表示在PLATFORM_A或PLATFORM_B平台编译
// H5
// #ifdef H5
// #endif
// 非 H5
// #ifndef H5
// #endif
// ifdef MP-WEIXIN
// #endif
// MP-WEIXIN 或 H5
// ifdef MP-WEIXIN || H5
// #endif
// APP-PLUS
// ifdef APP-PLUS
// #endif
// APP-NVUE
// #ifdef APP-NVUE
// #endif
// Vue2
// #ifdef VUE2
// #endif
// Vue3
// #ifdef VUE3
// #endif条件编译支持的平台:
H5MP-WEIXINAPP-PLUS
相应的环境变量可按如下方式获取:
// 可能值为 h5/mp-weixin/mp-alipay/mp-baidu/mp-taobao/mp-qq
process.env?.UNI_PLATFORM;图片无损压缩
APP-PLUS上使用plus.zip.compressImage, 文档MP-WEIXINH5
全局事件总线
uniapp 提供了以下 API, 可跨页面、跨组件进行事件监听、触发和移除
uni.$on监听指定事件uni.$emit触发指定事件uni.$once监听指定事件, 但触发一次后自动移除该监听, 而无需手动移除监听uni.$off移除指定事件, 如果不传入参数, 则移除APP级别的所有事件监听; 如果仅传入事件名, 则移除该事件名对应的所有监听; 如果传入了事件名和相应回调(同一引用), 则仅移除该事件回调的监听器.
登录和首页逻辑
pages.json中pages数组第一项设置为登录页- 用户未登录时显示登录页
- 用户已登录则自动跳转至首页
此时如果用户已登录, 重新进入微信小程序时会出现首页->登录页跳转动画, 首页会一闪而过, 可按如下方式解决:
- 在登录页中添加
loading, 再根据用户是否登录决定取消loading还是reLaunch到首页 - 设置空白页作为启动页, 再根据用户是否登录决定
reLaunch到登录页还是首页, 首页若有tabbar, 则必须使用uni.switchTab方法, 而不能是uni.navigateTo/uni.reLaunch/uni.redirectTo等方法
并发限制
浏览器默认并发请求限制数为 6, 可查阅 /net/socket/client_socket_pool_manager.cc
隐藏 canvas
canvas是原生组件, 始终在其他元素上方,display: none;不会隐藏, 可使用left: 99999px或在canvas外层嵌套一个元素并设置style="width: 0; height: 0; overflow: hidden;"
requestAnimationFrame 兼容
网络请求
- 小程序默认有访问域名限制, 需要在后台配置白名单
- 开发环境下可跳过域名校验, 生产环境则需要正确配置, 且必须发起
HTTPS请求
JSX / TSX
- 只支持
H5和APP, 不支持小程序, JSX/TSX 支持
监听滚动事件
在页面内监听滚动事件:
<script setup lang="ts">
import {onPageScroll} from '@dcloudio/uni-app';
onPageScroll(e => {
console.log('页面滚动了', e.scrollTop);
});
</script>在组件内监听页面滚动, 在对应页面添加滚动事件并使用 uni.$emit 发出滚动事件, 在子组件内使用 uni.$on 接收滚动事件。
或使用 scroll-view 标签
<script setup lang="ts">
const onScroll = (e) => {console.log('页面滚动了', e.scrollTop);}
</script>
<template>
<scroll-view scroll-y @scroll="onScroll">
</template>滚动穿透
滚动穿透即禁止蒙版下页面滚动:
<view @touchmove.stop.prevent></view>滚动吸顶
useLoading
须借助 @uni-helper/vite-plugin-uni-layouts 实现 loading 全局单例
onReady 和 onMounted
如果须获取元素或其位置信息, 页面须在 onReady 中获取, 组件须在 onMounted 中获取, 或使用 nextTick 确保元素已挂载
boundingClientRect 获取到 null:
- 在组件中增加
in(this)或in(getCurrentInstance()), 且须在onReady或onMounted中调用 - 动态
id确保首字母非数字, 否则微信小程序无法识别 - 末尾须有
.exec(), 否则回调均不执行
检查 API 兼容性
CSS
使用 CSS 检查:
.image {
aspect-ratio: 16 / 9;
}
@supports not (aspect-ratio: 16 / 9) {
}使用 JS 检查:
// 返回布尔值, 取决于当前浏览器的支持情况
CSS.supports('aspect-ratio', '16 / 9');JS
if (typeof requestIdleCallback !== 'undefined' && typeof window.requestIdleCallback === 'function') {
// 支持 requestIdleCallback
}
if (typeof document.body.scrollIntoView === 'function') {
// 支持 scrollIntoView
}
if ('scrollBehavior' in document.documentElement.style) {
// 支持 scrollBehavior
}获取元素信息
Vue2:
// 直接传入 this
uni.createSelectorQuery().in(this).select('.class');Vue3:
const instance = getCurrentInstance();
// 使用 getCurrentInstance() 代替 Vue2 中的 this
uni.createSelectorQuery().in(instance).select('.class');生命周期
uniapp
uniapp 的应用生命周期:
onLaunch仅在App.vue中存在, 全局仅触发一次onPageNotFound仅在APP启动时页面路径不存在时触发, 而不会在路由跳转时(如navigateTo)触发, 此时须另行处理onShow当uniapp启动或从后台进入前台时触发onHide当uniapp从前台进入后台时触发onError监听报错onThemeChange系统主题变化onUnhandledRejection监听未处理的Promise拒绝事件
uniapp 的页面生命周期:
onInit页面初始化onLoad页面加载成功时触发, 其参数为上一个页面传递的数据, 数据类型为对象ObjectonShow页面初次渲染完成onReady页面显示onHide页面隐藏onUnload页面卸载onResize窗口尺寸变化onPullDownRefresh顶部下拉, 常用于下拉刷新onReachBottom滚动触底事件, 常用于无限滚动列表追加数据onPageScroll页面滚动事件onTabItemTap点击tab时触发onNavigationBarButtonTap原生标题栏按钮点击事件onBackPress监听页面返回事件onShareAppMessage用户点击右上角分享按钮, 仅支持小程序平台上onShareTimeline用户点击右上角转发到朋友圈, 仅支持微信小程序onAddToFavorites用户点击右上角收藏, 仅支持微信小程序onNavigationBarSearchInputChanged原生标题栏搜索输入框文本变化事件onNavigationBarSearchInputConfirmed原生标题栏搜索输入框搜索事件, 用户点击软键盘的搜索按钮时触发onNavigationBarSearchInputClicked原生标题栏搜索输入框点击事件
Vue2
Vue3
Vue3 的生命周期:
onMountedonUnmountedonBeforeDestory
APP 热更新
TODO
配置启动页
在 pages.json 配置 condition:
{
"condition": {
"current": 1,
"list": [
{
"name": "Test Page 1",
"path": "subpackages/info-collection/info-collection"
},
{
"name": "Test Page 2",
"path": "subpackages/sample-database/sample-database"
}
]
}
}- 上述配置仅在开发调试时生效, 打包后无效, 打包后仍取
pages数组首项为启动页。 - 修改完
pages.json后重新启动项目, 即可在微信开发者工具顶部普通编译点击切换为Test Page 1或Test Page 2
自定义属性
<script>
const onClick = e => {
// custom
console.log(e.target.dataset.custom);
// custom-info
// 注意这里的驼峰写法会被转换为全小写
console.log(e.target.dataset.custominfo);
};
</script>
<template>
<view data-custom="custom" data-customInfo="custom-info" @click="onClick">自定义属性</view>
</template>使用 boundingClientRect 这个 API 也会获取到 dataset 属性值:
<script>
onMounted(() => {
const item = uni.createSelectorQuery().in(instance).select('#id');
item
.boundingClientRect(data => {
console.log(data.dataset);
})
.exec();
});
</script>自动更新版本号
版本号 x.y.z:
x表示Major, 主版本号, 大版本y表示Minor, 次版本号, 小版本z表示Patch,Bug Fix时自增
自动生成 CHANGELOG.md
安全区适配
IOS 全面屏底部会有小黑线, 影响元素可见性和用户可操作性, 为此须对其进行安全区适配。
以 safe-area-inset-bottom 为例:
.iPhone {
/* #ifndef H5 */
padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom, 20px); /* 兼容 iOS >= 11.2 */
/* #endif */
/* #ifdef H5 */
padding-bottom: 20px;
/* #endif */
}safe-area-inset-leftsafe-area-inset-rightsafe-area-inset-topsafe-area-inset-bottom
H5 上还需对 head meta 标签进行设置:
<head>
<meta name="viewport" content="viewport-fit=cover" />
</head>小程序中无须设置, 因其 viewport-fit 值默认为 cover。
环境区分
对于微信小程序:
// develop 开发版
// trial 体验版
// release 正式版
__wxConfig.envVersion;对于 H5:
// develepment 开发版
process.env.NODE_ENV;动态插槽
组件内动态插槽使用:
<!-- HACK: uni-app 处理动态 slot 名字不兼容, 需要使用不同的语法 -->
<!-- #ifdef H5 -->
<slot :name="`tab:${item.key}`"></slot>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<slot name="tab:{{item.key}}"></slot>
<!-- #endif -->组件内使用:
<view>
<!-- HACK: uni-app 处理动态 slot 名字不兼容, 需要使用不同的语法 -->
<!-- #ifdef H5 -->
<template v-for="item in list" :slot="`tab:${item.id}`">
<post-list :key="item.id" />
</template>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<template v-for="item in lits" slot="tab:professional:{{item.id}}">
<post-list :key="item.id" />
</template>
<!-- #endif -->
</view>动态样式
使用 v-bind:style="" 而非 :style="", 否则样式不生效
页面间通信
url单向通信:A页面向B页面传递参数:jsuni.navigateTo({url: '/pages/exapme/example?id=1'});B页面获取A页面传递的参数:jsexport default { onLoad(option) { console.log(option, option.id); } };全局变量
存储全局变量于
constant.js可挂载属性或方法至
Vue.prototype, 仅适用于vue文件 而不适用于nvue文件在
App.vue设置globalDatajsexport default { globalData: { example: 'example' } };在任何地方获取:
jsconsole.log(getApp().globalData.example);
本地存储
uni.getStorageSync和uni.setStorageSync全局事件总线
uni.$on、uni.$emit、uni.$once、uni.$off使用
getCurrentPages:jsconst pages = getCurrentPages(); const previousPageInstance = pages[pages.length - 2]?.$vm; // 获取上一页面的数据 console.log(previousPageInstance.exampleData); // 调用上一页面的方法 console.log(previousPageInstance.exampleMethod?.());状态管理
Vuex或Pinia
空格、换行处理
仅 text 标签可识别 \n 字符串并实现换行:
<template>
<text>{{ message }}</text>
</template>
<script>
export default {
data() {
return {
message: 'Line 1\nLine2\nLine3'
};
}
};
</script>TIP
- 仅
text标签支持\n换行,view等标签不支持 - 空格可输入
, 同样仅在text标签中支持 span标签会被uniapp自动转换为text标签
版本更新
小程序
主要使用 uni 提供的以下 API:
uni.getUpdateManager返回一个updateManager对象, 有以下几个方法:onCheckForUpdateonUpdateReadyonUpdateFailedapplyUpdate
TIP
上述 API 仅支持小程序平台, 不支持 H5 和 APP 平台, 代码建议使用条件编译包裹:
// #ifndef H5 || APP-PLUS
// rest of the code
// #endif并在 App.vue 文件 onLaunch 中尽可能最先调用
APP
杂项
页面根元素
- 微信小程序上页面根元素为
page标签 H5上页面根元素为uni-page-body标签 (类似web components)