目录
- 前言
- [一、uni-app 相关概念](#一、uni-app 相关概念)
- [二、uni-app 能运行到哪些端?](#二、uni-app 能运行到哪些端?)
- 三、技术本质(非常重要)
-
- [1、uni-app 的本质](#1、uni-app 的本质)
- [2、uni-app 的整体架构(技术本质的核心)](#2、uni-app 的整体架构(技术本质的核心))
- [3、uni-app 的技术核心要素](#3、uni-app 的技术核心要素)
-
- [(1)、Vue 响应式系统](#(1)、Vue 响应式系统)
- (2)、编译器机制(跨端本质)
- (3)、跨端运行时(Runtime)
- [(4)、原生桥接(App / 小程序)](#(4)、原生桥接(App / 小程序))
- (5)、条件编译(平台差异隔离)
- [4、uni-app 的跨端原理总结](#4、uni-app 的跨端原理总结)
- 四、核心组成
-
- 1、视图层(Vue)
- 2、跨端组件系统
- [3、跨端 API(uni.xxx)](#3、跨端 API(uni.xxx))
- [五、路由与页面模型(与 Vue SPA 不同)](#五、路由与页面模型(与 Vue SPA 不同))
- 六、生命周期(重点)
-
- [1、应用生命周期(App 级)](#1、应用生命周期(App 级))
- 2、页面生命周期(最常用、最容易踩坑)
- [3、Vue 组件生命周期(经常被混淆)](#3、Vue 组件生命周期(经常被混淆))
- [4、页面 vs 组件 生命周期对照表(重点)](#4、页面 vs 组件 生命周期对照表(重点))
- [5、Tab 页面特殊规则(90% 的坑)](#5、Tab 页面特殊规则(90% 的坑))
- 6、生命周期执行顺序(真实顺序)
- 7、数据请求放哪?
- [七、条件编译(uni-app 的灵魂)](#七、条件编译(uni-app 的灵魂))
-
- 1、条件编译的本质原理(重要)
- 2、条件编译的三种写法(核心)
-
- [(1)、JS 中的条件编译(最常用)](#(1)、JS 中的条件编译(最常用))
- (2)、模板(template)中的条件编译
- (3)、样式(CSS)中的条件编译
- [3、条件编译 vs 运行时判断(本质区别)](#3、条件编译 vs 运行时判断(本质区别))
- 4、最常见的使用场景(实战)
- 5、条件编译的"高危误区"(非常重要)
- 6、最佳实践(重点)
- [八、在 App 端的原理与机制](#八、在 App 端的原理与机制)
-
- [1、App 端原理(很多人不懂)](#1、App 端原理(很多人不懂))
-
- [(1)、uni-app 的 App 端不是 WebView](#(1)、uni-app 的 App 端不是 WebView)
- (2)、渲染机制详解(核心)
- [2、为什么 CSS 受限?](#2、为什么 CSS 受限?)
- [3、JS 运行机制(你以为在浏览器,其实不是)](#3、JS 运行机制(你以为在浏览器,其实不是))
- [4、JSBridge:uni-app 的命脉](#4、JSBridge:uni-app 的命脉)
-
- [(1)、什么是 JSBridge?](#(1)、什么是 JSBridge?)
- [(2)、为什么频繁 setData 会卡?](#(2)、为什么频繁 setData 会卡?)
- [5、uni API 在 App 端是怎么实现的?](#5、uni API 在 App 端是怎么实现的?)
- 6、页面缓存机制(非常容易踩坑)
- [7、App 端与 H5 的根本差异对比](#7、App 端与 H5 的根本差异对比)
- [8、uni-app App 端性能优化核心原则](#8、uni-app App 端性能优化核心原则)
- [9、原生插件机制(App 独有)](#9、原生插件机制(App 独有))
- [10、云打包 vs 本地打包(原理差异)](#10、云打包 vs 本地打包(原理差异))
- 九、开发建议(经验总结)
- 十、问题汇总
-
- [1、架构 & 认知类问题(根本性)](#1、架构 & 认知类问题(根本性))
-
- [(1)、误以为 uni-app = Web(最致命)](#(1)、误以为 uni-app = Web(最致命))
- [(2)、把条件编译当 if 用](#(2)、把条件编译当 if 用)
- (3)、业务逻辑到处写 #ifdef
- 2、生命周期问题
-
- [(1)、数据放在 mounted,不刷新](#(1)、数据放在 mounted,不刷新)
- [(2)、onUnload 不执行(特别是 Tab 页)](#(2)、onUnload 不执行(特别是 Tab 页))
- [(3)、onLoad 操作 DOM 报错](#(3)、onLoad 操作 DOM 报错)
- [3、样式 & 布局问题(最折磨人)](#3、样式 & 布局问题(最折磨人))
-
- [(1)、position: fixed 行为不一致](#(1)、position: fixed 行为不一致)
- [(2)、CSS 在 App 端不生效](#(2)、CSS 在 App 端不生效)
- [(3)、rpx / px 混乱](#(3)、rpx / px 混乱)
- [4、性能问题(App / 小程序)](#4、性能问题(App / 小程序))
-
- (1)、页面卡顿、列表滑动掉帧
- [(2)、App 真机卡,模拟器顺](#(2)、App 真机卡,模拟器顺)
- [5、路由 & 页面栈问题](#5、路由 & 页面栈问题)
- [6、App 端特有问题](#6、App 端特有问题)
- [7、请求 & 网络问题](#7、请求 & 网络问题)
-
- [(1)、request 在小程序和 App 行为不同](#(1)、request 在小程序和 App 行为不同)
- [(2)、文件上传 / 下载失败](#(2)、文件上传 / 下载失败)
- [8、构建 & 环境问题](#8、构建 & 环境问题)
-
- (1)、依赖包不兼容小程序
- [(2)、Vite / HBuilderX 构建差异](#(2)、Vite / HBuilderX 构建差异)
- [9、调试 & 排错问题](#9、调试 & 排错问题)
-
- [(1)、App 端无法调试](#(1)、App 端无法调试)
- (2)、小程序报错信息少
- [(3)、为什么 App 端有些 bug「只在真机出现」?](#(3)、为什么 App 端有些 bug「只在真机出现」?)
- [10、团队 & 维护类问题](#10、团队 & 维护类问题)
-
- [(1)、uni-app 项目越写越难维护](#(1)、uni-app 项目越写越难维护)
- 十一、市面上主流的多端框架选型对比
前言
uni-app 必须使用 Vue(Vue2 / Vue3),它是 Vue 世界的跨端之王。
uni-app 官网
一、uni-app 相关概念
uni-app 是一个基于 Vue 的跨端应用开发框架,一套代码可编译到多端(H5 / 小程序 / App / 快应用)。
核心目标:
- 开发一次,多端运行
- 最大化复用 Vue 技术栈
- 对原生能力做统一封装
二、uni-app 能运行到哪些端?
| 平台 | 支持情况 | 说明 |
|---|---|---|
| H5 | ✅ 完整支持 | 本质是 Vue SPA |
| 微信小程序 | ✅ 主战场 | 企业最常用 |
| 支付宝 / 百度 / 字节 / QQ 小程序 | ✅ | API 有差异 |
| App(iOS / Android) | ✅ | 基于 DCloud 原生容器 |
| 快应用 | ⚠️ 较少使用 | 市场衰退 |
| 鸿蒙 | ⚠️ 新支持 | 生态仍在发展 |
现实结论:
- 80% 项目 = 微信小程序 + H5
- App 端通常是 "有条件才上"
三、技术本质(非常重要)
uni-app 的本质 = Vue + 编译器 + 跨端运行时 + 原生桥接 + 条件编译的多端统一框架。
1、uni-app 的本质
uni-app 本质上是一个"编译时多端适配 + 运行时响应式管理"的混合跨端平台。
也就是说:
- 不是运行时做适配(if 判断)
- 不是 Web 套壳 App
- 核心是"编译时生成不同平台原生代码 + JS 逻辑层控制"
| 平台 | 编译结果 | 描述 |
|---|---|---|
| H5 | Vue + DOM | 普通 Vue SPA |
| 小程序 | WXML + WXSS + JS | 编译成原生小程序代码 |
| App | JS + 原生桥(JSBridge) | JS 运行 + 原生组件 |
2、uni-app 的整体架构(技术本质的核心)

- JS 层:业务逻辑 + Vue 响应式系统
- 渲染层:各端原生组件或 Web 容器
- JSBridge / 编译器:通信 + 条件编译 + API 适配
3、uni-app 的技术核心要素
(1)、Vue 响应式系统
- 页面 / 组件的数据变动 → Vue 响应式触发更新
- App / 小程序端通过 JSBridge 把变化同步到原生渲染层
本质上 Vue 是"跨端数据驱动 UI 的统一语言"
(2)、编译器机制(跨端本质)
uni-app 的核心是 编译器,不是运行时:
typescript
源码(.vue / JS / CSS)
↓ 编译
目标端代码(微信 / 支付宝 / H5 / App)
- 条件编译:#ifdef 让不同端生成不同代码
- 组件映射:统一标签 → 各端原生组件
- API 封装:统一接口 → 各端原生实现
核心:编译期解决差异,运行期只做业务逻辑
(3)、跨端运行时(Runtime)
- App 端:JS 在 JS 引擎里执行 → JSBridge 通知原生渲染
- 小程序端:编译成小程序模板 + JS 逻辑
- H5:直接生成 DOM + JS
核心:"同一套 Vue 代码 → 不同平台最终表现不同"
(4)、原生桥接(App / 小程序)
- App:JS → JSBridge → Native API
- 小程序:JS → 小程序容器 API
- H5:直接调用浏览器 API
桥接决定性能瓶颈,例如频繁 setData → JSBridge 调用 → 卡顿
(5)、条件编译(平台差异隔离)
typescript
// #ifdef MP-WEIXIN
wx.getSystemInfoSync()
// #endif
// #ifdef H5
navigator.userAgent
// #endif
- 编译期生成不同端代码
- 不会增加运行时开销
- 核心价值 = 隔离差异,保持业务统一
4、uni-app 的跨端原理总结
| 技术点 | 本质原理 | 举例 |
|---|---|---|
| 跨端渲染 | 编译器 + 映射表 | <view> → 原生 View / div |
| 数据绑定 | Vue 响应式 | this.msg = 'xx' → 页面更新 |
| API 适配 | 条件编译 + 封装 | uni.getSystemInfo() → 微信/支付宝/APP不同实现 |
| 页面生命周期 | Vue 生命周期 + 小程序生命周期映射 | onLoad / onShow / onReady |
| App 端通信 | JSBridge | uni.showToast() → 原生 Toast |
四、核心组成
1、视图层(Vue)
- Vue 2 / Vue 3(推荐 Vue 3)
- .vue 单文件组件
- 支持 setup、reactive、ref
typescript
<template>
<view class="box">{{ msg }}</view>
</template>
<script setup>
const msg = 'Hello uni-app'
</script>
⚠️ 注意:
- 没有 div / span
- 用的是 view / text / image
2、跨端组件系统
| 组件 | 说明 |
|---|---|
| view | 万能容器 |
| text | 文本 |
| image | 图片 |
| scroll-view | 滚动 |
| swiper | 轮播 |
| button | 按钮 |
不能随意用 HTML 标签
3、跨端 API(uni.xxx)
- uni.request()
- uni.navigateTo()
- uni.getStorage()
- uni.showToast()
| 类型 | 示例 |
|---|---|
| 网络 | request / uploadFile |
| 路由 | navigateTo / redirectTo |
| 存储 | setStorage |
| 设备 | getSystemInfo |
| UI | showModal |
底层自动适配各端原生 API。
五、路由与页面模型(与 Vue SPA 不同)
uni-app 的页面是"声明式"的
json
// pages.json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
}
]
}
页面跳转 API:
| API | 说明 |
|---|---|
| navigateTo | 入栈 |
| redirectTo | 替换 |
| switchTab | tab 页 |
| reLaunch | 重启 |
⚠️ 注意:没有 vue-router
六、生命周期(重点)
typescript
应用生命周期(App)
├─ onLaunch
├─ onShow
├─ onHide
│
页面生命周期(Page)
├─ onLoad
├─ onShow
├─ onReady
├─ onHide
└─ onUnload
│
组件生命周期(Vue)
├─ setup
├─ onMounted
├─ onUnmounted
关键认知:
App / Page / Component 是三套完全不同的生命周期体系
1、应用生命周期(App 级)
定义在 App.vue:
typescript
export default {
onLaunch(options) {},
onShow(options) {},
onHide() {}
}
| 生命周期 | 触发时机 | 典型用途 |
|---|---|---|
| onLaunch | 应用冷启动 | 初始化、登录态恢复 |
| onShow | 前台显示 | 检查登录、埋点 |
| onHide | 进入后台 | 保存状态、停止轮询 |
注意点(重点):
- 只触发一次:onLaunch
- 切前后台都会触发:onShow / onHide
- 页面跳转 不会 触发 App 生命周期
2、页面生命周期(最常用、最容易踩坑)
定义在 pages/*.vue:
typescript
export default {
onLoad(query) {},
onShow() {},
onReady() {},
onHide() {},
onUnload() {}
}
| 生命周期 | 是否只一次 | 说明 |
|---|---|---|
| onLoad | ✅ | 页面首次加载 |
| onShow | ❌ | 每次显示 |
| onReady | ✅ | 页面初次渲染完成 |
| onHide | ❌ | 页面被遮挡 |
| onUnload | ✅ | 页面被销毁 |
(1)、onLoad(只执行一次)
typescript
onLoad(query) {
console.log(query.id)
}
- 接收路由参数
- 类似 Vue 的 created
- 不能依赖 DOM
常见错误:
typescript
// ❌ DOM 还没渲染
this.$refs.xxx
(2)、onShow(最常用)
typescript
onShow() {
this.fetchData()
}
- 每次页面可见都会执行
- 返回页面、Tab 切换都会触发
推荐放数据刷新逻辑
(3)、onReady(只一次)
typescript
onReady() {
// DOM 已可用
}
- 页面首次渲染完成
- 适合 DOM 操作、组件初始化
(4)、onHide(不销毁)
typescript
onHide() {
clearInterval(this.timer)
}
- 页面仍在内存
- 只是被遮挡
⚠️ 页面缓存仍在!
(5)、onUnload(彻底销毁)
typescript
onUnload() {
console.log('页面销毁')
}
- 页面出栈
- 释放资源
3、Vue 组件生命周期(经常被混淆)
在 setup() 或 Options API 中:
typescript
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {})
onUnmounted(() => {})
| 生命周期 | 触发时机 |
|---|---|
| setup | 组件创建 |
| onMounted | 组件挂载 |
| onUnmounted | 组件卸载 |
⚠️ 组件生命周期 ≠ 页面生命周期
4、页面 vs 组件 生命周期对照表(重点)
| 场景 | 页面生命周期 | 组件生命周期 |
|---|---|---|
| 页面首次进入 | onLoad → onShow → onReady | setup → onMounted |
| 页面切换 | onHide | ❌ |
| 返回页面 | onShow | ❌ |
| 页面销毁 | onUnload | onUnmounted |
5、Tab 页面特殊规则(90% 的坑)
| 行为 | 结果 |
|---|---|
| 切换 tab | 触发 onHide / onShow |
| tab 页面 | 不会 onUnload |
| tab 页面 | 常驻内存 |
6、生命周期执行顺序(真实顺序)
- 页面首次进入:
typescript
App.onLaunch
App.onShow
Page.onLoad
Page.onShow
Component.setup
Component.onMounted
Page.onReady
- 页面切换返回:
typescript
Page.onHide
Page.onShow
⚠️ 页面切换 不会触发 App 生命周期
7、数据请求放哪?
| 类型 | 生命周期 |
|---|---|
| 初始化请求 | onLoad |
| 需要刷新 | onShow |
| DOM 操作 | onReady |
| 清理资源 | onUnload |
七、条件编译(uni-app 的灵魂)
1、条件编译的本质原理(重要)
typescript
源代码
↓
uni 编译器
↓
根据平台裁剪代码
↓
目标端代码
被裁剪掉的代码:
不会进入 bundle
不会被执行
不会报错
2、条件编译的三种写法(核心)
uni-app 支持的所有宏(汇总)
| 宏 | 含义 |
|---|---|
| H5 | Web |
| APP-PLUS | App |
| MP-WEIXIN | 微信 |
| MP-ALIPAY | 支付宝 |
| MP-BAIDU | 百度 |
| MP-TOUTIAO | 字节 |
| MP-QQ | |
| MP-KUAISHOU | 快手 |
| MP-JD | 京东 |
(1)、JS 中的条件编译(最常用)
typescript
// #ifdef MP-WEIXIN
console.log('微信小程序')
// #endif
// #ifdef H5
console.log('H5')
// #endif
多条件:
typescript
// #ifdef MP-WEIXIN || H5
console.log('微信 or H5')
// #endif
(2)、模板(template)中的条件编译
html
<template>
<!-- #ifdef MP-WEIXIN -->
<view>微信专属</view>
<!-- #endif -->
<!-- #ifdef H5 -->
<view>H5 专属</view>
<!-- #endif -->
</template>
⚠️ 注意:
- 不能写 JS 表达式
- 只是"代码存在与否"
(3)、样式(CSS)中的条件编译
css
/* #ifdef H5 */
.container {
height: 100vh;
}
/* #endif */
/* #ifdef MP-WEIXIN */
.container {
height: 100%;
}
/* #endif */
3、条件编译 vs 运行时判断(本质区别)
| 对比项 | 条件编译 | if 判断 |
|---|---|---|
| 阶段 | 编译期 | 运行期 |
| 多端代码 | ❌ | ✅ |
| 性能 | 无影响 | 有分支 |
| 安全性 | 高 | 可能报错 |
能用条件编译,坚决不用 if。
4、最常见的使用场景(实战)
场景 1:API 差异(最典型)
typescript
let res
// #ifdef MP-WEIXIN
res = wx.getSystemInfoSync()
// #endif
// #ifdef H5
res = window.navigator.userAgent
// #endif
场景 2:组件差异
html
<template>
<!-- #ifdef APP-PLUS -->
<native-map />
<!-- #endif -->
<!-- #ifdef H5 -->
<web-map />
<!-- #endif -->
</template>
场景 3:样式适配
css
/* #ifdef APP-PLUS */
.safe-area {
padding-bottom: env(safe-area-inset-bottom);
}
/* #endif */
场景 4:权限 & 审核规避(现实)
typescript
// #ifdef MP-WEIXIN
// 不允许使用的 API
// #endif
5、条件编译的"高危误区"(非常重要)
误区 1:大量业务逻辑条件编译
typescript
// ❌ 不要这样
// #ifdef MP-WEIXIN
doA()
// #endif
// #ifdef H5
doB()
// #endif
✅ 正确方式:
typescript
import { doSomething } from '@/utils/platform'
doSomething()
误区 2:组件内部大量 #ifdef
会让组件不可维护
✅ 抽平台组件:
typescript
components/
├─ map-weixin.vue
├─ map-h5.vue
└─ index.vue
误区 3:条件编译嵌套
typescript
// ❌ 易炸
// #ifdef MP-WEIXIN
// #ifdef H5
// #endif
6、最佳实践(重点)
(1)、平台能力集中封装
typescript
utils/
└─ platform.ts
typescript
export function getSystemInfo() {
// #ifdef MP-WEIXIN
return wx.getSystemInfoSync()
// #endif
// #ifdef H5
return navigator.userAgent
// #endif
}
(2)、页面不直接写条件编译
页面只关心"业务",不关心"平台"。
(3)、平台组件隔离
例如:
typescript
components/
└─ map/
├─ index.vue
├─ map.wechat.vue
└─ map.h5.vue
八、在 App 端的原理与机制
- uni-app App 端是"原生优先"的混合架构,而不是 Web 套壳。
- 理解 JSBridge,才能写出不卡的 App。
1、App 端原理(很多人不懂)
(1)、uni-app 的 App 端不是 WebView
uni-app 的 App 端是:JS 逻辑层(Vue) + 原生 UI 渲染层 + JSBridge 通信层的混合架构。
typescript
Vue(JS) // 逻辑层
↓
JSBridge // 通信层
↓
iOS / Android 原生 // 渲染层
(2)、渲染机制详解(核心)
假设有一段 uni-app 的 UI 代码如下:
html
<view class="box">
<text>Hello</text>
</view>
其编译后:
- view → 原生容器 View
- text → 原生 TextView / UILabel
⚠️ 注意:
- 不是 HTML
- 也不是 DOM
2、为什么 CSS 受限?
| 原因 | 说明 |
|---|---|
| 原生组件 | 不支持完整 CSS |
| 跨端统一 | 取交集 |
| 性能考虑 | 避免重排 |
复杂布局在 App 端 成本更高
3、JS 运行机制(你以为在浏览器,其实不是)
JS 引擎:
| 平台 | 引擎 |
|---|---|
| Android | V8 |
| iOS | JavaScriptCore |
JS 与 UI 不在同一线程。
JS 逻辑流程:
typescript
JS 执行业务逻辑
↓
生成虚拟结构
↓
通过 JSBridge
↓
通知原生层更新 UI
通信是异步的。
4、JSBridge:uni-app 的命脉
(1)、什么是 JSBridge?
JSBridge = JS 与原生之间的通信通道。
uni-app 中的例子:
typescript
uni.showToast({
title: '成功'
})
上述 js 代码通过 JSBridge 被转换为原生可执行的代码。
typescript
JS → JSBridge → Native Toast API
(2)、为什么频繁 setData 会卡?
- 每次状态变化
- 都要过 JSBridge
- 大量数据 = 大量通信
这就是 App 端性能瓶颈的来源。
5、uni API 在 App 端是怎么实现的?
例:uni.getSystemInfo()
typescript
uni.getSystemInfo()
↓
JS API 封装
↓
JSBridge
↓
Native API
↓
回调给 JS
每个 uni.xxx 都是:
- JS 封装
- 原生实现
- 桥接回调
6、页面缓存机制(非常容易踩坑)
App 端页面行为:
| 行为 | 结果 |
|---|---|
| navigateTo | 页面入栈 |
| 返回 | 页面复用 |
| tab 页面 | 永不销毁 |
| 内存不足 | 系统强杀 |
你以为 onUnload 会执行,其实未必。
7、App 端与 H5 的根本差异对比
| 维度 | App 端 | H5 |
|---|---|---|
| 渲染 | 原生 | DOM |
| JS 引擎 | 独立 | 浏览器 |
| CSS | 子集 | 完整 |
| API | 原生桥接 | Web API |
| 性能瓶颈 | JSBridge | DOM |
8、uni-app App 端性能优化核心原则
- 减少 JSBridge 通信
- 合并状态更新
- 避免频繁 setData
- 少用复杂组件嵌套
- 原生组件层级深 = 慢
- 合理使用原生插件
- 重能力交给原生
- 长列表用虚拟列表
- 避免全量渲染
9、原生插件机制(App 独有)
插件结构:
typescript
nativeplugins/
└─ my-plugin/
├─ android/
├─ ios/
└─ js/
JS 调用:
typescript
const plugin = uni.requireNativePlugin('my-plugin')
plugin.doSomething()
这是 uni-app 真正的原生扩展能力
10、云打包 vs 本地打包(原理差异)
| 方式 | 特点 |
|---|---|
| 云打包 | 快速、方便 |
| 本地打包 | 可定制、可调试 |
九、开发建议(经验总结)
1、开发者必须理解的核心原则
- 跨端本质 = 编译期 + 运行时分离
- 业务逻辑统一,差异用条件编译或组件封装
- 性能瓶颈 = JSBridge + 原生渲染层
- 生命周期映射不同端,Tab 页面与 App 页面不同
2、工程实践建议
- API 封装:不要直接写 #ifdef 分散在业务里
- 组件封装:跨端差异用组件隔离
- 状态管理:Pinia / Vuex 控制业务状态,减少桥接调用
- 性能优化:减少频繁更新、长列表虚拟化、避免深层嵌套
3、最佳实践
uni-app ≠ 一劳永逸
- 以微信小程序为主端设计
- 条件编译集中管理
- 样式尽量简单
- App 端后期再补
- 公共逻辑抽 hooks
十、问题汇总
1、架构 & 认知类问题(根本性)
(1)、误以为 uni-app = Web(最致命)
表现:
- 用 document / window
- 复杂 CSS 在 App 端失效
- H5 正常,App 崩
根因:
- App 端是 原生渲染 + JSBridge
- 不存在 DOM
解决:
- 只用 uni 组件 & API
- App 端复杂能力交给原生插件
(2)、把条件编译当 if 用
表现:
typescript
if (isWechat) {
wx.xxx()
}
问题:非微信端直接报错
正确方式:
typescript
// #ifdef MP-WEIXIN
wx.xxx()
// #endif
(3)、业务逻辑到处写 #ifdef
表现:
- 代码碎裂
- 维护灾难
解决:
- 平台能力集中封装
- 页面只写业务
2、生命周期问题
(1)、数据放在 mounted,不刷新
原因:页面不会重新 mounted
正确:
typescript
onShow() {
this.fetch()
}
(2)、onUnload 不执行(特别是 Tab 页)
原因:Tab 页面不会销毁
解决:清理逻辑放 onHide
(3)、onLoad 操作 DOM 报错
原因:页面未渲染完成
正确:
typescript
DOM 操作放 onReady
3、样式 & 布局问题(最折磨人)
(1)、position: fixed 行为不一致
原因:原生组件限制
解决:
- 尽量使用 sticky
- 避免复杂 fixed
(2)、CSS 在 App 端不生效
原因:原生渲染不支持完整 CSS
建议:
- 布局尽量简单
- 避免伪元素 / 复杂动画
(3)、rpx / px 混乱
建议:
- 统一 rpx
- 禁止混用
4、性能问题(App / 小程序)
(1)、页面卡顿、列表滑动掉帧
根因:
- 频繁 setData
- JSBridge 通信过多
解决:
- 合并状态更新
- 虚拟列表
- 节流防抖
(2)、App 真机卡,模拟器顺
原因:
- 真机线程调度复杂
- JSBridge 成本真实
解决:
- 真机为准
- 降低频繁响应式更新
5、路由 & 页面栈问题
(1)、页面返回数据丢失
原因:页面缓存被销毁
解决:Pinia / Storage 存储状态
(2)、页面栈溢出
原因:navigateTo 过多
解决:合理使用 redirectTo / reLaunch
6、App 端特有问题
(1)、打包后功能异常
原因:
- 权限未配置
- 插件未注册
解决:manifest.json 检查
(2)、原生能力无法调用
原因:云打包能力有限
解决:
- 本地打包
- 使用原生插件
7、请求 & 网络问题
(1)、request 在小程序和 App 行为不同
原因:底层网络实现不同
解决:
- 超时 & 重试机制
- 错误统一处理
(2)、文件上传 / 下载失败
原因:各端限制不同
解决:
- 条件编译
- 分端测试
8、构建 & 环境问题
(1)、依赖包不兼容小程序
原因:使用了 Node / DOM API
解决:检查依赖是否纯 JS
(2)、Vite / HBuilderX 构建差异
建议:
- 统一构建方式
- 避免混用配置
9、调试 & 排错问题
(1)、App 端无法调试
解决:
- 真机调试
- 日志上报
(2)、小程序报错信息少
建议:
- try/catch
- 统一错误上报
(3)、为什么 App 端有些 bug「只在真机出现」?
| 原因 | 说明 |
|---|---|
| 模拟器 | 非真实系统 |
| 线程调度 | 真机更复杂 |
| 性能瓶颈 | JSBridge |
| 内存管理 | 系统级 |
10、团队 & 维护类问题
(1)、uni-app 项目越写越难维护
根因:
- 没有层级设计
- 平台差异污染业务
解决架构:

十一、市面上主流的多端框架选型对比
uni-app、Taro、React Native、Flutter、Electron 的技术选型对比表:
| 维度 | uni-app | Taro | React Native | Flutter | Electron |
|---|---|---|---|---|---|
| 核心定位 | 国内多端业务 | React 跨小程序 | 原生 App | 高一致性 UI | 桌面端应用 |
| 跨端方式 | 编译时 | 编译时 | 运行时 | 自绘引擎 | WebView |
| 主要语言 | Vue2 / Vue3 | React | React | Dart | HTML / JS |
| 是否必须 Vue / React | 必须 Vue | 必须 React | 必须 React | 必须 Dart | 无 |
| JSX 支持 | ❌ | ✅ | ✅ | ❌ | ✅ |
| 响应式模型 | Vue reactivity | React state | React state | 自带 | Web |
| H5(Web) | ✅⭐⭐⭐⭐⭐ | ✅⭐⭐⭐⭐ | ⚠️(需适配) | ⚠️(Flutter Web) | ❌ |
| 微信小程序 | ✅⭐⭐⭐⭐⭐ | ✅⭐⭐⭐⭐ | ❌ | ❌ | ❌ |
| 其他小程序 | ✅ | ✅ | ❌ | ❌ | ❌ |
| iOS App | ✅ | ⚠️ | ✅⭐⭐⭐⭐ | ✅⭐⭐⭐⭐⭐ | ❌ |
| Android App | ✅ | ⚠️ | ✅⭐⭐⭐⭐ | ✅⭐⭐⭐⭐⭐ | ❌ |
| Windows 桌面 | ❌ | ❌ | ❌ | ⚠️ | ✅⭐⭐⭐⭐⭐ |
| macOS 桌面 | ❌ | ❌ | ❌ | ⚠️ | ✅⭐⭐⭐⭐⭐ |
| Linux 桌面 | ❌ | ❌ | ❌ | ⚠️ | ✅ |
| 渲染机制 | 原生模板 / 原生组件 | 小程序模板 | 原生组件 + JSBridge | Skia 自绘 | Chromium |
| 性能上限 | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| UI 一致性 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 原生体验 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ❌(非原生) | ❌ |
| 动画能力 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ |
| CSS / 样式自由度 | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 第三方 UI 生态 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 国内生态成熟度 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 国际生态成熟度 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 企业采用率(国内) | ⭐⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 学习成本 | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐ |
| 开发效率 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 调试难度 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ |
| 构建复杂度 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 长期维护成本 | ⭐⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐ |
| 是否适合中小团队 | ✅⭐⭐⭐⭐⭐ | ⚠️ | ❌ | ❌ | ⚠️ |
| 是否适合大型长期项目 | ⚠️ | ⚠️ | ✅ | ✅ | ⚠️ |
| 典型优势 | 多端 + 小程序王者 | React 用户友好 | 原生体验好 | 性能 & 一致性 | 桌面端唯一解 |
| 最大短板 | 复杂 UI 受限 | App 能力弱 | 维护成本高 | 无法做小程序 | 体积大、性能差 |
| 最适合场景 | 国内多端业务 | React + 小程序 | 重交互 App | 高性能 UI | 桌面工具 |