📚 全栈开发学习系列
阶段一:编程基础 ✅ 已完成
01-08 路线总览 → Python → C语言 → 数据结构 → Git → Linux
阶段二:Web 全栈 ✅ 已完成
09-16 HTML → CSS → JavaScript → HTTP → FastAPI → PostgreSQL → 认证授权 → 博客系统
阶段三:前端深化 ✅ 已完成
17-25 React → TypeScript → Next.js → Tailwind → 工程化 → 测试 → API测试 → 状态管理
阶段四:跨平台 App
✓ 26 跨平台开发:React Native 与 Expo
✓ 27 React Native 进阶:导航与状态管理
✓ 28 Flutter 跨平台开发:Dart 语言与 Widget 体系
✓ 29 Flutter 进阶:状态管理与网络请求
30 Uni-app:小程序开发入门(当前篇)
阶段五:扩展 + 部署
Tauri(桌面)→ Docker Compose → CI/CD → 域名部署
Uni-app:小程序开发入门
难度:进阶 | 技术栈:Uni-app 5.24 / Vue 3.4 / Vite 5 / Pinia | 阶段四第 5 篇
读完本篇你将能:理解 Uni-app 跨端编译的核心原理与多端输出能力,使用 Vue 3 + Vite 搭建小程序项目,掌握 uni-app 组件体系和 API 命名规范,通过条件编译实现各平台差异化逻辑,搭建一个完整的列表-详情小程序页面,并建立从 React Native/Flutter 到小程序的跨框架心智迁移路径。
前三篇我们研究了原生级跨平台方案——React Native 走 JavaScript 桥接,Flutter 走 Dart 自绘。但中国互联网有一个独特的生态:小程序。微信小程序、支付宝小程序、抖音小程序……每个平台都有自己的语法和限制,如果逐个开发,成本堪比做三套独立 App。
Uni-app 就是为这个场景而生的——一套 Vue 3 代码,编译到微信小程序、支付宝小程序、H5、App 等十多个平台。它不像 RN/Flutter 那样追求"原生体验",而是追求"一次开发,多端运行"的开发效率。对前端开发者来说,Vue 语法 + 小程序生态的组合,学习曲线比从零学 Dart 平缓得多。本篇我们用 Uni-app 5.24 搭建第一个小程序项目。
📑 本文目录
01Uni-app 概述:跨端编译原理与生态
02项目结构:Vue 3 + Vite 脚手架
03组件体系:uni-app 组件与原生小程序映射
04条件编译:多端差异化的核心武器
05常见错误与动手练习
Uni-app 概述:跨端编译原理与生态
什么是 Uni-app
Uni-app 是 DCloud 推出的跨端开发框架(当前编译器版本 5.24),基于 Vue.js 语法——你写的是 Vue 3 组件,编译时会被转换成各平台可运行的代码。编译到微信小程序时输出 .wxml/.wxss/.js,编译到支付宝时输出 .axml/.acss/.js,编译到 H5 时输出标准 HTML/CSS/JS。
和 React Native、Flutter 的定位不同——RN 和 Flutter 瞄准的是"替代原生 App 开发",而 Uni-app 瞄准的是"小程序 + H5 + App 全覆盖"。对国内团队来说,小程序是必做的渠道,Uni-app 的价值在于:用一套代码同时覆盖微信小程序、支付宝小程序、抖音小程序、百度小程序、QQ 小程序、H5、App 等十多个平台。
| 对比维度 |
React Native |
Flutter |
Uni-app |
| 底层渲染 |
原生组件 |
Skia 自绘 |
各平台原生渲染 |
| 开发语言 |
JavaScript / TypeScript |
Dart |
Vue 3 / TypeScript |
| 目标平台 |
iOS / Android |
iOS / Android / Web / 桌面 |
小程序矩阵 + H5 + App |
| 性能 |
接近原生 |
原生级 |
取决于目标平台 |
| 生态重心 |
全球 App 开发 |
Google 生态 |
国内小程序生态 |
编译原理:从 Vue 到小程序
Uni-app 的编译器做了三件事:模板转换、样式转换、API 适配。以微信小程序为例:
- 模板层:Vue 的
<template> 被转换为微信的 .wxml,v-if → wx:if,v-for → wx:for
- 样式层:
.vue 中的 <style> 被提取为 .wxss,并自动处理 rpx 单位
- 逻辑层:Vue 3 的响应式系统被适配到小程序的 Page/Component 生命周期,setup 函数转换为 data + methods
这套"编译时转换"的思路和 RN/Flutter 完全不同——RN 是运行时桥接,Flutter 是运行时自绘,Uni-app 是编译时转译。好处是运行时零开销、直接使用各平台原生能力,坏处是各平台的 API 差异必须在编译层处理,遇到某个平台独有的特性需要写条件编译。
💡 小贴士
Uni-app 的名字含义是"universal application"——通用应用。它的核心哲学不是"做最好的单平台体验",而是"用最低成本覆盖最多平台"。如果你的业务重心在国内小程序生态(微信+支付宝+抖音),Uni-app 是效率最高的选择;如果追求极致性能和原生体验,Flutter 或原生开发更合适。选型的核心指标是:你的用户主要在哪个渠道?
项目结构:Vue 3 + Vite 脚手架
Uni-app 官方推荐两种创建方式:HBuilderX 可视化创建(适合新手)和 CLI 命令行创建(适合工程化团队)。我们以 CLI 方式为例,基于 Vite 5 + Vue 3.4 + TypeScript 搭建项目。
创建项目
Shell - 创建 Uni-app 项目
# 使用官方 create-uniapp 脚手架
npx degit dcloudio/uni-preset-vue#vite-ts my-miniprogram
# 进入目录安装依赖
cd my-miniprogram
npm install
# 开发:微信小程序
npm run dev:mp-weixin
# 开发:H5(浏览器预览)
npm run dev:h5
脚手架会生成一套完整的 Vue 3 + Vite + TypeScript 项目。开发微信小程序时,dev:mp-weixin 命令会在 dist/dev/mp-weixin/ 下生成微信小程序代码,你用微信开发者工具打开这个目录即可预览。
目录结构解析
项目结构
my-miniprogram/
├── src/
│ ├── pages/ // 页面目录(每个子目录一个页面)
│ │ ├── index/ // 首页
│ │ │ └── index.vue
│ │ └── detail/ // 详情页
│ │ └── detail.vue
│ ├── components/ // 公共组件
│ ├── static/ // 静态资源(图片等)
│ ├── stores/ // Pinia 状态管理
│ ├── App.vue // 应用入口(全局样式/生命周期)
│ ├── main.ts // 入口文件
│ ├── pages.json // 页面路由+全局配置(核心文件)
│ └── manifest.json // 应用配置(appid、权限等)
├── vite.config.ts // Vite 配置
└── package.json
对 Vue 开发者来说,这个结构很熟悉——就是标准的 Vite + Vue 项目。新增的核心文件是 pages.json,它定义了所有页面的路径、窗口样式、TabBar 配置等,相当于小程序的路由表 + 全局配置。
pages.json 与页面注册
在小程序中,每个页面必须在 pages.json 中注册,否则无法跳转。这和 Vue Router 的路由表概念一致,但格式是 JSON 而非 JS:
JSON - pages.json 配置
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情"
}
}
],
"globalStyle": {
"navigationBarBackgroundColor": "#16a34a",
"navigationBarTextStyle": "white"
}
}
|
pages 数组的第一个元素就是小程序的启动页。每个页面对象包含 path(页面路径,不带 .vue 后缀)和 style(页面独有的窗口样式)。globalStyle 是全局默认样式,单个页面的 style 会覆盖全局配置。
一个 Vue 3 页面组件
Uni-app 的页面就是标准的 Vue 3 SFC(单文件组件),使用 Composition API 的 setup 语法糖。下面是一个列表页的完整示例:
Vue - 列表页 index.vue
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
<template>
<view class="container">
<view
v-for="item in list"
:key="item.id"
class="list-item"
@click="goDetail(item.id)"
>
<text class="title">{{ item.title }}</text>
<text class="desc">{{ item.desc }}</text>
</view>
</view>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
const list = ref<{id: number; title: string}[]>([])
|
注意模板里用的不是 HTML 标签(div、p、span),而是 uni-app 的组件(view、text)——这些是跨端组件,编译时会被转换为对应平台的标签。
组件体系:uni-app 组件与原生小程序映射
基础组件映射
Uni-app 提供了一套跨端组件,底层会被编译为各平台的原生组件。对前端开发者来说,最核心的认知转换是:你熟悉的 HTML 标签在这里有对应的 uni-app 组件名。
| HTML 标签 |
uni-app 组件 |
微信小程序 |
说明 |
| div |
view |
view |
最基础的容器组件 |
| span / p |
text |
text |
文本组件,唯一可长按选中的组件 |
| img |
image |
image |
图片组件,mode 属性控制裁剪模式 |
| button |
button |
button |
按钮,open-type 可调用微信能力 |
| input |
input / textarea |
input / textarea |
表单输入组件 |
| ul / li |
list(不推荐) |
— |
用 view + v-for 替代,小程序无列表标签 |
rpx:响应式像素
小程序有一个独特的尺寸单位 rpx(responsive pixel)。它的设计思想和 Flutter 的 逻辑像素 类似——规定屏幕宽度为 750rpx,元素尺寸按比例自适应。
CSS - rpx 自适应布局
.list-item {
width: 710rpx; /* 占屏幕宽度 710/750 ≈ 94.7% */
height: 160rpx; /* 所有屏幕上比例一致 */
padding: 20rpx;
margin: 0 20rpx 20rpx;
}
在 iPhone 6(375px 宽)上,1rpx = 0.5px;在 iPhone 14 Pro Max(430px 宽)上,1rpx ≈ 0.573px。Uni-app 编译器会自动把 rpx 转换为各平台对应的单位——小程序端保留 rpx,H5 端转为 vw,App 端转为 px。这让你不用手动写媒体查询就能实现响应式。
uni.xxx API 体系
除了组件,uni-app 还封装了一套统一的 API,命名格式为 uni.方法名()。这些 API 在底层会调用对应平台的原生方法——微信小程序调用 wx.xxx(),支付宝调用 my.xxx(),H5 端用浏览器 API 模拟。
TypeScript - 常用 uni API
|
1
2
3
4
5
6
7
8
9
10
11
12
|
// 页面跳转(对应 wx.navigateTo)
uni.navigateTo({
url: '/pages/detail/detail?id=123'
})
// Toast 提示(对应 wx.showToast)
uni.showToast({
title: '操作成功',
icon: 'success'
})
// 本地存储(对应 wx.setStorageSync)
uni.setStorageSync('token', 'abc123')
|
💡 小贴士
Uni-app 的 API 设计和微信小程序几乎一一对应——你看到 uni.showToast,对应的就是 wx.showToast。记忆方法很简单:把 wx. 换成 uni.,90% 的 API 就能直接跨端使用。如果某个 API 在特定平台不存在,uni-app 会自动降级或报错。
条件编译:多端差异化的核心武器
理想情况下,一套代码跑所有平台。但现实是:各平台能力不对等——微信小程序有"微信支付",支付宝有"芝麻信用",抖音有"拍视频"。如果强行用最低公约数开发,每个平台的特色能力都浪费了。条件编译就是用来解决这个矛盾的。
条件编译原理
条件编译的本质是:在代码中用特殊注释标记"某段代码只在某平台编译",编译器根据目标平台决定保留或删除这段代码。语法格式为 #ifdef 平台名(如果是某平台)和 #endif(结束条件)。
Vue - 模板中的条件编译
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<template>
<view class="share-section">
<!-- #ifdef MP-WEIXIN -->
<button open-type="share">分享到微信</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<button @click="shareToAlipay">分享到支付宝</button>
<!-- #endif -->
</view>
</template>
<script setup>
// #ifdef MP-WEIXIN
import { onShareAppMessage } from '@dcloudio/uni-app'
// #endif
</script>
|
编译到微信小程序时,MP-WEIXIN 块保留、MP-ALIPAY 块被删除;编译到支付宝时相反。编译到 H5 时,两块都被删除——因为 H5 平台既不是微信也不是支付宝。
常用条件编译常量
| 条件编译常量 |
对应平台 |
MP-WEIXIN |
微信小程序 |
MP-ALIPAY |
支付宝小程序 |
MP-BAIDU |
百度小程序 |
MP-TOUTIAO |
抖音/头条小程序 |
H5 |
H5 网页 |
APP |
App(iOS/Android) |
MP |
所有小程序平台 |
💡 小贴士
条件编译的设计哲学是"大部分代码共享,少量代码差异化"。一个健康的 uni-app 项目,通用代码应该占 80% 以上,条件编译块控制在 20% 以内。如果你的项目里到处都是 #ifdef,说明抽象层做得不够——应该把平台差异封装成统一的 API 接口,各平台分别实现,业务层只调用统一接口。这和设计模式中的"策略模式"思路完全一致。
样式中的条件编译
条件编译不仅能用在模板和脚本里,样式中同样可用。比如微信小程序支持某个 CSS 属性而支付宝不支持,你可以用条件编译给不同平台写不同的样式:
CSS - 样式条件编译
.card {
padding: 20rpx;
border-radius: 16rpx;
/* #ifdef MP-WEIXIN */
backdrop-filter: blur(10px);
/* #endif */
/* #ifdef MP-ALIPAY */
background-color: rgba(255, 255, 255, 0.9);
/* #endif */
}
微信小程序端用毛玻璃效果,支付宝端用半透明白色背景兜底——编译器会根据目标平台自动选择正确的样式块,另一个块完全删除,不会出现在最终产物中。
常见错误与动手练习
高频踩坑清单
| 错误现象 |
根因 |
修复 |
| 页面跳转 404 |
pages.json 中未注册该页面 |
在 pages 数组中添加页面路径 |
| text 组件内文字不换行 |
文字直接放在 view 里而非 text 中 |
所有文字内容必须用 text 组件包裹 |
| image 图片变形拉伸 |
未设置 mode 属性,默认值 scaleToFill |
设置 mode="aspectFit" 或 "widthFix" |
| navigateTo 跳转无效 |
跳转到了 tabBar 页面 |
TabBar 页面必须用 switchTab 跳转 |
| 条件编译不生效 |
注释格式写错(少了空格或 #endif) |
检查 #ifdef 和 #endif 是否配对且格式正确 |
⚠️ 常见错误
文字不换行是小程序开发的经典新手坑。在 HTML 中你可以把文字直接放在 div 里,浏览器自动换行。但在小程序中,文字必须放在 <text> 组件内才能正常换行。直接写在 view 里的文字会溢出容器或被截断——这是因为小程序的渲染层和逻辑层分离,text 组件才有文字排版的能力。养成习惯:所有文字一律用 text 包裹。
动手练习
练习 1:基础(Hello World 小程序)
使用 create-uniapp 脚手架创建一个 Vue 3 + TypeScript 项目,修改首页为一个个人介绍页面:包含头像(image 组件)、姓名(text 组件)、简介(多行 text)。配置 pages.json 设置导航栏标题为"我的主页"。用微信开发者工具打开 dist/dev/mp-weixin 目录,确认能正常运行。
练习 2:进阶(列表 + 详情跳转)
创建一个文章列表页和文章详情页。列表页用 v-for 渲染 10 篇文章卡片,点击卡片调用 uni.navigateTo 跳转到详情页并传递文章 id。详情页通过 onLoad 生命周期接收参数,展示对应文章内容。在 pages.json 中注册两个页面,并用 uni.setStorageSync 实现"阅读历史"功能——每打开一篇文章就把 id 存到本地。
练习 3:挑战(多端条件编译 + Pinia)
在练习 2 的基础上增加以下功能:引入 Pinia 管理文章列表状态(替代本地存储),用条件编译实现——微信小程序端显示"分享到朋友圈"按钮(使用 button open-type="share"),H5 端显示"复制链接"按钮(调用 navigator.clipboard),两端都用同一个 share 方法调用。目标:理解状态管理在 uni-app 中的用法 + 条件编译的实战场景。
🏷️ 知识回顾
Uni-app 5.24Vue 3.4Vite 5TypeScript编译时转译
pages.jsonmanifest.jsonview / text / imagerpx 响应式像素
uni.navigateTouni.showToastuni.setStorageSyncPinia
条件编译 #ifdefMP-WEIXIN微信开发者工具跨端编译
下一篇预告
本篇完成了跨平台 App 阶段的最后一站——Uni-app 小程序开发入门。至此,阶段四全部完结:我们从 React Native 原生级跨端出发,走过 Flutter Dart 自绘方案,最终抵达 Uni-app 小程序生态。下一篇将进入阶段五:扩展与部署,从桌面端 Tauri 开始,学习用 Web 技术构建桌面应用,然后进入 Docker Compose 容器编排、CI/CD 自动化、域名部署等后端工程化内容——从前端到后端、从移动端到桌面端,全栈开发的完整知识图谱即将合拢。