📚 全栈开发学习系列
阶段一:编程基础 ✅ 已完成
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 适配。以微信小程序为例:

这套"编译时转换"的思路和 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 自动化、域名部署等后端工程化内容——从前端到后端、从移动端到桌面端,全栈开发的完整知识图谱即将合拢。