为什么写这篇:UniApp 是国内"Vue 写多端"最成熟的方案——2023 年统计有 600 万+ 开发者。一个团队写一套 Vue 代码,就能发到微信小程序、抖音小程序、H5、iOS/Android——节省 60%+ 人力。本文从环境到实战完整走通。
适用读者:要快速开发多端小程序的前端;从 Vue 转 UniApp 的工程师;想了解跨端框架对比的负责人。
前置知识:Vue 2 基础;会用 Vue CLI。
目录
- UniApp 是什么
- 环境准备
- 创建项目
- 目录结构
- 条件编译:跨端兼容的关键
- UniApp 生命周期
- 页面与路由
- 常用 API:网络 / 存储 / 位置
- 跨端注意事项与坑
- UniApp vs Taro vs React Native vs Flutter
1. UniApp 是什么
DCloud 推出的"一次开发,多端发布"框架。Vue 语法 + 小程序能力 + H5/Web 能力——一套代码同时发布到:
| 端 | 支持度 |
|---|
| iOS / Android(App) | ✅ 真机原生 |
| H5 | ✅ Web |
| 微信小程序 | ✅ 完整支持 |
| 支付宝小程序 | ✅ |
| 百度小程序 | ✅ |
| 抖音小程序 | ✅ |
| QQ 小程序 | ✅ |
| 快应用 | ✅ |
| 钉钉小程序 | ✅ |
| 飞书小程序 | ✅ |
| 各厂商快应用 | ✅ 10+ 个 |
Why 选择 UniApp:
- Vue 语法——学习成本低,国内前端主力栈
- 生态完整——uni-ui / uView UI / uni-ui-plus 等组件库丰富
- IDE 配套——HBuilderX(官方 IDE)开箱即用,无需配 Webpack/Vite
- 社区活跃——DCloud 论坛 + 大量 GitHub 仓库
2. 环境准备
2.1 推荐方式:HBuilderX(官方 IDE)
下载 HBuilderX——免费、可视化、内置模拟器。
优点:零配置、模板多、可视化运行、发布
缺点:基于 VSCode 内核改的,部分插件兼容性一般
2.2 命令行方式(适合习惯 Webpack/Vite 的工程师)
1
2
3
4
| # 全局装 vue-cli
npm install -g @vue/cli
vue -V # @vue/cli 5.0.x
|
版本要求(推荐):
| 工具 | 版本 |
|---|
| Node | 14.21.3(官方推荐) |
| npm | 6.14.18 |
| yarn | 1.22.19 |
| vue-cli | 5.0.8 |
Node 兼容性:UniApp 早期版本不兼容 Node 18+。用 nvm 切到 Node 14/16 是最稳的。
3. 创建项目
3.1 命令行(vue-cli)
1
| vue create -p dcloudio/uni-preset-vue my-project
|
交互式:
1
2
| ? 请选择 uni-app 模板: hello uni-app # 官方默认模板
? 是否使用 TypeScript: No # 也可选 Yes
|
镜像加速:vue create 拉模板可能慢——HBuilderX 内置模板不走这一步。
3.2 HBuilderX
文件 → 新建 → 项目 → uni-app → 选模板(默认 / Hello uni-app / 登录模板 / 地图模板 / 蓝牙模板…)。
3.3 运行
1
2
3
4
5
6
7
8
9
10
11
| # 浏览器(H5)
npm run dev:h5
# 微信小程序(需配微信开发者工具路径)
npm run dev:mp-weixin
# 支付宝小程序
npm run dev:mp-alipay
# App(需配模拟器或真机)
npm run dev:app
|
4. 目录结构
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
| my-project/
├── pages/ # 页面文件(自动注册路由)
│ ├── index/
│ │ └── index.vue
│ ├── about/
│ │ └── about.vue
│ └── list/
│ └── list.vue
├── static/ # 静态资源(不会被编译)
│ ├── images/
│ └── icons/
├── components/ # 自定义公共组件
│ └── my-card/
│ └── my-card.vue
├── common/ # 公共 JS / CSS
│ ├── api.js
│ └── uni.css
├── store/ # Vuex
│ └── index.js
├── utils/ # 工具函数
│ └── request.js
├── App.vue # 应用配置(生命周期、全局样式)
├── main.js # 入口 JS
├── manifest.json # 应用配置(appid、版本、权限)
├── pages.json # 页面路由 + tabBar + navigationBar
├── uni.scss # 全局 SCSS 变量
└── package.json
|
pages.json 是 UniApp 的"路由 + 导航 + tabBar"配置文件——比 Vue Router 简单。
5. 条件编译:跨端兼容的关键
UniApp 的杀手锏——用注释语法实现编译时的跨端代码分叉。
5.1 模板条件
1
2
3
4
5
6
7
8
9
10
11
| <!-- #ifdef H5 -->
<view>这段只在 H5 显示</view>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<view>只在微信小程序显示</view>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<view>只在 App 端显示</view>
<!-- #endif -->
|
5.2 JS 条件
1
2
3
4
5
6
7
| // #ifdef H5
console.log('H5 only')
// #endif
// #ifndef MP-WEIXIN
console.log('非微信端都执行')
// #endif
|
5.3 CSS 条件
1
2
3
4
5
6
7
| /* #ifdef H5 */
.header { padding-top: 44px; } /* H5 顶部避开浏览器栏 */
/* #endif */
/* #ifdef MP-WEIXIN */
.header { padding-top: 0; }
/* #endif */
|
5.4 常见条件编译关键字
| 关键字 | 含义 |
|---|
H5 | H5 端 |
MP-WEIXIN | 微信小程序 |
MP-ALIPAY | 支付宝小程序 |
MP-BAIDU | 百度小程序 |
MP-TOUTIAO | 抖音/头条小程序 |
MP-QQ | QQ 小程序 |
MP | 所有小程序 |
APP-PLUS | App 端(iOS / Android) |
APP-PLUS-NVUE | App nvue 页面(原生渲染) |
APP-NVUE | 同上 |
APP-VUE | App 端 vue 页面 |
5.5 实战:H5 显示分享按钮 / 小程序隐藏
1
2
3
4
5
6
7
8
9
| <view class="actions">
<!-- #ifdef H5 -->
<button @click="shareToWeibo">分享到微博</button>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<button open-type="share">微信分享</button>
<!-- #endif -->
</view>
|
6. UniApp 生命周期
6.1 应用生命周期(App.vue)
| 钩子 | 说明 |
|---|
onLaunch | 应用初始化(全局只触发一次) |
onShow | 应用启动或从后台进入前台 |
onHide | 应用从前台进入后台 |
onError | 报错时触发 |
1
2
3
4
5
6
7
8
9
10
11
12
| // App.vue
export default {
onLaunch() {
console.log('App Launch')
},
onShow() {
console.log('App Show')
},
onHide() {
console.log('App Hide')
}
}
|
6.2 页面生命周期
| 钩子 | 说明 |
|---|
onLoad | 页面加载(接收 options 参数) |
onShow | 页面显示 |
onReady | 页面初次渲染完成 |
onHide | 页面隐藏 |
onUnload | 页面卸载 |
onPullDownRefresh | 用户下拉刷新 |
onReachBottom | 页面上拉触底 |
onShareAppMessage | 微信分享 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // pages/index/index.vue
export default {
data() {
return { count: 0 }
},
onLoad(options) {
console.log('页面加载,参数:', options)
},
onShow() {
console.log('页面显示')
},
onPullDownRefresh() {
this.refresh()
},
methods: {
refresh() {
// 重新拉数据
uni.stopPullDownRefresh()
}
}
}
|
vs Vue 生命周期:UniApp 用 onLoad / onShow 替代 Vue 的 mounted——因为 App 切后台再回来时,页面不一定重新加载。
7. 页面与路由
7.1 pages.json 配置
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
| {
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"enablePullDownRefresh": true
}
},
{
"path": "pages/about/about",
"style": {
"navigationBarTitleText": "关于"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "white",
"navigationBarTitleText": "我的 App",
"navigationBarBackgroundColor": "#42b883",
"backgroundColor": "#f8f8f8"
},
"tabBar": {
"color": "#7A7E83",
"selectedColor": "#42b883",
"backgroundColor": "#FFFFFF",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/icons/home.png",
"selectedIconPath": "static/icons/home-active.png"
},
{
"pagePath": "pages/about/about",
"text": "关于",
"iconPath": "static/icons/user.png",
"selectedIconPath": "static/icons/user-active.png"
}
]
}
}
|
7.2 路由 API
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| // 跳转到新页面(保留当前页面)
uni.navigateTo({ url: '/pages/detail/detail?id=123' })
// 重定向(关闭当前页面,跳到新页面)
uni.redirectTo({ url: '/pages/login/login' })
// 切换 tabBar 页面
uni.switchTab({ url: '/pages/index/index' })
// 返回上一页(delta:返回层数)
uni.navigateBack({ delta: 1 })
// 关闭所有页面,打开到应用内的某个页面
uni.reLaunch({ url: '/pages/index/index' })
|
7.3 接收参数
1
2
3
4
5
6
7
8
9
10
| // 跳转
uni.navigateTo({ url: '/pages/detail/detail?id=123&name=test' })
// 接收(在 onLoad 钩子)
export default {
onLoad(options) {
console.log(options.id) // '123'
console.log(options.name) // 'test'
}
}
|
8. 常用 API:网络 / 存储 / 位置
8.1 网络请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| // 推荐封装
function request(url, method, data) {
return new Promise((resolve, reject) => {
uni.request({
url: 'https://api.example.com' + url,
method,
data,
header: {
'Authorization': 'Bearer ' + uni.getStorageSync('token')
},
success: (res) => {
if (res.statusCode === 200) resolve(res.data)
else reject(res)
},
fail: (err) => reject(err)
})
})
}
// 用法
const data = await request('/user/profile', 'GET')
|
8.2 数据存储
1
2
3
4
5
6
7
8
| // 同步
uni.setStorageSync('key', 'value')
uni.getStorageSync('key') // 'value'
uni.removeStorageSync('key')
// 异步
await uni.setStorage({ key: 'k', data: 'v' })
const { data } = await uni.getStorage({ key: 'k' })
|
H5 端底层是 localStorage——大小限制 5-10MB
8.3 位置
1
2
3
4
5
6
7
| uni.getLocation({
type: 'gcj02',
success: (res) => {
console.log('纬度', res.latitude)
console.log('经度', res.longitude)
}
})
|
8.4 系统信息
1
2
3
4
| const info = uni.getSystemInfoSync()
console.log(info.platform) // 'ios' | 'android' | 'devtools'
console.log(info.screenWidth)
console.log(info.statusBarHeight) // 状态栏高度
|
8.5 弹窗
1
2
3
4
| uni.showToast({ title: '保存成功', icon: 'success' })
uni.showModal({ title: '提示', content: '确定删除?', success: (r) => {
if (r.confirm) console.log('用户点了确定')
}})
|
9. 跨端注意事项与坑
9.1 v-if 替代 v-show
1
2
| <!-- 小程序不支持 v-show(性能差),用 v-if -->
<view v-if="show">内容</view>
|
9.2 标签必须闭合
1
2
3
4
5
| <!-- ❌ 自闭合 -->
<input ...>
<!-- ✅ 自闭合 -->
<input ... />
|
9.3 没有 <img> 标签
1
2
3
4
5
| <!-- ❌ 用 img -->
<img src="..." />
<!-- ✅ 用 image -->
<image src="..." />
|
9.4 没有 <a> 标签
1
2
3
4
5
| <!-- ❌ 用 a 标签跳转 -->
<a href="/about">关于</a>
<!-- ✅ 用 navigator 组件或 JS API -->
<navigator url="/pages/about/about">关于</navigator>
|
9.5 没有 <div>,用 <view>;没有 <span>,用 <text>
1
2
3
| <view class="container">
<text class="title">{{ msg }}</text>
</view>
|
9.6 CSS 单位用 rpx(响应式像素)
1
2
3
4
5
| .container {
width: 750rpx; /* iPhone 6 下等于 375px,全屏 */
padding: 20rpx;
font-size: 28rpx;
}
|
rpx = 屏幕宽度的 1/750——所有设备按比例缩放。
9.7 小程序无法直接操作 DOM
1
2
3
4
5
| // ❌ 错误
document.querySelector('.foo').classList.add('active')
// ✅ 正确:通过 Vue 响应式
this.isActive = true
|
9.8 微信小程序的登录流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
| // 1. 调 wx.login() 拿 code
uni.login({
provider: 'weixin',
success: (loginRes) => {
const code = loginRes.code
// 2. 把 code 发到自家后端,换 session_key + openid
uni.request({
url: 'https://api.example.com/wechat/login',
method: 'POST',
data: { code },
success: (res) => {
uni.setStorageSync('token', res.data.token)
}
})
}
})
|
10. UniApp vs Taro vs React Native vs Flutter
| 维度 | UniApp | Taro 3 | React Native | Flutter |
|---|
| 语法 | Vue 2/3 | React | React | Dart |
| 渲染 | WebView / 小程序 | WebView / 小程序 / RN | Native | 自绘引擎 |
| 性能 | 中(WebView 限制) | 中 | 接近原生 | 最优 |
| 多端覆盖 | 10+ 端 | 8 端 | 仅 iOS/Android | 仅 iOS/Android |
| 学习成本 | 低(Vue) | 中(React + Taro API) | 中 | 高(Dart) |
| 生态 | 国内最丰富 | 中(京东系) | Facebook 系 | Google 系 |
| 包体积 | 1-3MB | 1-3MB | 5-10MB | 5-15MB |
| TypeScript | 支持 | 支持 | 强 | 强 |
| 大厂使用 | 阿里、京东、字节、腾讯 | 京东、网易、美团 | Facebook、Airbnb | Google、阿里 |
选型建议:
- 国内小程序 + H5 + App 全部都要 → UniApp(首选)
- 团队熟悉 React + 多端 → Taro 3
- 追求 iOS/Android 性能 + 团队有原生经验 → React Native
- 极致性能 + 复杂动画 + 跨端 UI 一致 → Flutter
- 只需要 H5 / Web → 直接 Vue/React + Vite,不要用跨端框架
小结
UniApp 入门核心:
- 环境:HBuilderX(IDE) 或 vue-cli(命令行)
- 创建:
vue create -p dcloudio/uni-preset-vue 选 hello uni-app 模板 - 路由:pages.json(不是 Vue Router)
- 跨端:条件编译
<!-- #ifdef MP-WEIXIN --> 是关键 - API:全部走
uni.xxx 命名空间(uni.request / uni.navigateTo / uni.getLocation) - 坑:无 DOM、无 a/div/img、必须用 view/text/image、CSS 用 rpx
下一步:学 uni-ui 组件库(官方)、uView UI(社区最流行)、uCharts(图表)、即时通讯(融云 / 环信 UniApp SDK)、App 上架(iOS App Store + 安卓各大市场)。
参考资料