嘿,朋友!如果你现在正盯着屏幕上闪烁的光标发呆,手里攥着“微信小程序”、“APP”、“H5网页”这几个词感到头大,那咱们得好好聊聊了。以前,想做一个多端应用,你得懂原生Java/Kotlin搞Android,得啃Swift搞iOS,还得写一堆HTML/CSS/JS去适配浏览器。这就像是为了喝杯牛奶,不得不先养一头牛,再杀牛取奶——太折腾了,对吧?
UniApp 的出现,简直就是给开发者发了一根“魔法棒”。它让你只需用一套 Vue.js 的代码,就能同时生成微信小程序、APP(iOS/Android)和 H5。今天,我不跟你扯那些枯燥的理论定义,咱们直接上手。我会像带你逛自家后院一样,带你从零开始,把这套流程摸得透透的。哪怕你是第一次接触前端,只要跟着步骤走,最后看着自己的代码变成手机里能跑的小程序,那种成就感,啧啧,绝对让你上瘾。
第一步:磨刀不误砍柴工,环境搭建其实没那么可怕
很多新手卡在第一步,觉得安装环境像装系统一样复杂。其实,UniApp 的核心是基于 Vue 的,所以我们首选 HBuilderX。为什么不用 VS Code?因为 HBuilderX 是 DCloud 官方出品的 IDE,它对 UniApp 的支持是“亲儿子”级别的,自带云打包、真机调试,开箱即用,省去了配置 Webpack、Node 版本冲突等一堆让人抓狂的问题。
1. 下载与安装 HBuilderX
去 DCloud 官网下载 HBuilderX 的 正式版(注意:别下成 AppCloud 版,那是给企业团队协作用的,个人开发选标准版即可)。安装过程就像安装微信一样简单,一路“下一步”。
2. 创建第一个项目
打开 HBuilderX,点击左上角的 文件 -> 新建 -> 项目。
在弹出的窗口中:
- 选择 uni-app。
- 模板选择 默认模板(Hello UniApp)。
- 项目名称随便起,比如
MyFirstUniApp。 - 勾选 使用 Vue3(现在的趋势是 Vue3 + Composition API,性能更好,代码更简洁,强烈建议从 Vue3 起步)。
- 点击“创建”。
看着文件夹里那一堆文件,别慌。我们只需要关注两个核心:
pages.json:这是你的地图,决定了有哪些页面,以及页面的样式。manifest.json:这是你的身份证,决定了这个 App 叫什么名字、图标长什么样、支持哪些平台。
3. 运行起来看看
在工具栏上方,你会看到几个小图标。
- 点击 运行到浏览器:选择 Chrome,你会看到一个漂亮的 Hello World 页面。这说明你的基础环境没问题。
- 点击 运行到小程序模拟器:选择微信开发者工具。这时候,HBuilderX 会自动唤起你电脑上安装的微信开发者工具,并在里面渲染出页面。
这里有个小坑要注意:如果点击运行后,微信开发者工具没反应或者报错,通常是因为你没有安装“微信开发者工具”,或者没有将其设置为 HBuilderX 的外部程序。去设置里找到“运行配置”,确保微信开发者工具的路径是正确的。
第二步:读懂“骨架”与“血肉”,理解 UniApp 的结构
UniApp 的项目结构看起来和普通的 Vue 项目有点像,但有几个关键区别,理解了它们,你就掌握了主动权。
1. 目录结构解析
MyFirstUniApp/
├── pages/ # 页面文件夹
│ └── index/ # 首页目录
│ └── index.vue # 首页文件
├── static/ # 静态资源文件夹(放图片、字体等)
├── components/ # 自定义组件文件夹(推荐放这里)
├── uni_modules/ # 插件市场模块(后面会讲怎么用它)
├── App.vue # 应用配置,全局生命周期
├── main.js # Vue 初始化入口
├── manifest.json # 应用配置,用于打包及平台特定配置
└── pages.json # 页面配置,路由及窗口样式
- pages/index/index.vue:这是你的主战场。每一个
.vue文件由三部分组成:<template>(模板)、<script>(逻辑)、<style>(样式)。这和 Vue 单文件组件一模一样。 - pages.json:这是 UniApp 特有的。在原生 Vue Router 里,你需要写路由配置;但在 UniApp 里,页面路径必须在这里注册。而且,你可以直接在这里配置每个页面的导航栏颜色、背景色,甚至弹窗效果。
2. 生命周期:知道什么时候该干什么
作为一个“老鸟”,你得知道页面什么时候加载,什么时候卸载。UniApp 的生命周期分为两类:应用生命周期 和 页面生命周期。
应用生命周期(在
App.vue中):onLaunch: 应用初始化完成时触发,全局只触发一次。适合做登录验证、获取配置。onShow: 应用启动,或从后台进入前台时触发。onHide: 应用从前台进入后台时触发。
页面生命周期(在具体的
.vue页面中):onLoad: 页面加载时触发。一个页面只会调用一次,可以拿到上个页面navigateTo传递的参数。这是获取参数最常用的地方!onShow: 页面显示/切入前台时触发。每次打开页面都会调用,适合刷新数据。onReady: 页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。onHide: 页面隐藏/切入后台时触发。如redirectTo或tabBar切换。onUnload: 页面卸载时触发。如navigateTo的重定向或redirectTo的跳转。
实战技巧:如果你在 onLoad 里拿到了 ID,想在 onShow 里刷新数据,记得把请求封装成一个方法,哪里需要调哪里。
第三步:动手写代码,用 Vue 语法征服世界
好了,环境有了,结构懂了,现在我们来写点真的。假设我们要做一个简单的“待办事项”列表,这在任何多端开发中都是经典的“Hello World”。
1. 编写页面逻辑
打开 pages/index/index.vue,清空里面的内容,我们从头写起。
<template>
<!-- view 相当于 HTML 中的 div,text 相当于 span -->
<view class="container">
<view class="header">
<text class="title">我的待办清单</text>
</view>
<!-- 输入框区域 -->
<view class="input-group">
<input
class="todo-input"
v-model="newTodo"
placeholder="输入新的任务..."
@confirm="addTodo"
/>
<button class="add-btn" type="primary" size="mini" @click="addTodo">添加</button>
</view>
<!-- 列表区域 -->
<view class="list-container">
<block v-for="(item, index) in todoList" :key="index">
<view class="todo-item" :class="{ completed: item.done }">
<checkbox-group @change="checkChange(index)">
<label>
<checkbox :value="String(item.id)" :checked="item.done" color="#007AFF"/>
</label>
</checkbox-group>
<text class="todo-text">{{ item.text }}</text>
<text class="delete-btn" @click="deleteTodo(index)">删除</text>
</view>
</block>
<view v-if="todoList.length === 0" class="empty-tip">
<text>暂无任务,快去添加吧~</text>
</view>
</view>
</view>
</template>
<script setup>
import { ref, reactive } from 'vue';
// 响应式数据定义
const newTodo = ref('');
const todoList = ref([
{ id: 1, text: '学习 UniApp', done: false },
{ id: 2, text: '开发微信小程序', done: true }
]);
// 添加任务
const addTodo = () => {
if (!newTodo.value.trim()) {
uni.showToast({ title: '请输入内容', icon: 'none' });
return;
}
const newItem = {
id: Date.now(), // 简单的时间戳作为ID
text: newTodo.value,
done: false
};
todoList.value.unshift(newItem); // 添加到头部
newTodo.value = ''; // 清空输入框
uni.showToast({ title: '添加成功', icon: 'success' });
};
// 删除任务
const deleteTodo = (index) => {
// 弹出确认框
uni.showModal({
title: '提示',
content: '确定要删除这条任务吗?',
success: function (res) {
if (res.confirm) {
todoList.value.splice(index, 1);
}
}
});
};
// 切换完成状态
const checkChange = (index) => {
// 注意:checkbox-group 的 change 事件返回的是选中项的值数组
// 但因为我们是在 item 上绑定的 click,这里简化处理,实际项目中可能需要更复杂的逻辑
// 这里为了演示简单,我们在 template 里用了 checkbox-group 包裹 label,
// 实际上 UniApp 的 checkbox-group 是批量管理的。
// 让我们修正一下上面的 template 逻辑,使其更符合 UniApp 习惯:
// 由于 checkbox-group 需要 value 和 checked 配合,且 onChange 返回所有选中值,
// 这里为了代码简洁,我们直接用点击事件模拟勾选状态变化,
// 或者更严谨地,使用 checkbox-group 的 bindchange。
// 【修正】:为了演示清晰,我们重新设计一个简单的勾选逻辑
todoList.value[index].done = !todoList.value[index].done;
};
</script>
<style scoped>
.container {
padding: 20rpx;
background-color: #f5f5f5;
min-height: 100vh;
}
.header {
margin-bottom: 30rpx;
}
.title {
font-size: 36rpx;
font-weight: bold;
color: #333;
}
.input-group {
display: flex;
align-items: center;
background: #fff;
padding: 20rpx;
border-radius: 10rpx;
margin-bottom: 30rpx;
box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.05);
}
.todo-input {
flex: 1;
font-size: 28rpx;
margin-right: 20rpx;
}
.add-btn {
width: 120rpx;
height: 70rpx;
line-height: 70rpx;
font-size: 26rpx;
}
.list-container {
background: #fff;
border-radius: 10rpx;
overflow: hidden;
}
.todo-item {
display: flex;
align-items: center;
padding: 30rpx 20rpx;
border-bottom: 1rpx solid #eee;
}
.todo-item.completed .todo-text {
text-decoration: line-through;
color: #999;
}
.todo-text {
flex: 1;
font-size: 30rpx;
color: #333;
margin-left: 20rpx;
}
.delete-btn {
color: #ff4d4f;
font-size: 26rpx;
padding: 10rpx;
}
.empty-tip {
text-align: center;
padding: 50rpx 0;
color: #999;
font-size: 28rpx;
}
</style>
等等,上面的代码里关于 checkbox 的部分有点绕,让我给你捋一捋 UniApp 里的表单控件。
在 UniApp 中,checkbox-group 是一个容器,里面放多个 checkbox。当用户勾选时,bindchange 事件会返回所有被选中的 checkbox 的 value。
为了让你更容易理解,我把上面的 checkChange 逻辑优化一下,使用更标准的 checkbox-group 写法:
<!-- 在 template 中替换 list-container 部分 -->
<view class="list-container">
<block v-for="(item, index) in todoList" :key="index">
<view class="todo-item" :class="{ completed: item.done }">
<!-- 这里使用 checkbox-group 包裹单个 checkbox 是不对的,
通常 checkbox-group 包裹多个 checkbox。
但如果每个 item 独立控制,我们可以直接用 @click 改变状态,
或者使用 checkbox-group 并动态计算选中值。
为了简单直观,我们采用最通用的做法:
不依赖 checkbox-group 的自动收集,而是手动管理 done 状态。
但为了展示 UI 效果,我们用 checkbox 组件绑定 done 属性。
-->
<checkbox
:value="String(item.id)"
:checked="item.done"
color="#007AFF"
@click.stop="toggleDone(index)"
/>
<text class="todo-text">{{ item.text }}</text>
<text class="delete-btn" @click.stop="deleteTodo(index)">删除</text>
</view>
</block>
<!-- ... 其他代码不变 ... -->
</view>
<script setup>
// ... 前面代码不变 ...
// 修改 toggleDone 方法
const toggleDone = (index) => {
todoList.value[index].done = !todoList.value[index].done;
};
</script>
关键点解释:
- rpx 单位:你可能注意到了样式里用的是
rpx。这是 UniApp 推荐的响应式单位。750rpx 等于屏幕宽度。这样写,无论你在 iPhone SE 还是 iPhone 14 Pro Max 上,布局都不会乱。 - uni API:
uni.showToast,uni.showModal这些都是 UniApp 提供的原生能力封装。它们在不同端(微信、H5、App)会自动调用对应的原生控件或模拟实现,保证体验一致。 - Scoped 样式:加上
<style scoped>可以防止样式污染,只作用于当前组件。
第四步:跨端发布的秘密武器——条件编译
这是 UniApp 最强大的功能之一。虽然我们希望“一次编写,处处运行”,但现实是残酷的。微信小程序不允许某些 Web API,APP 需要申请权限,H5 需要处理浏览器兼容性问题。
这时候,条件编译 就登场了。它允许你在代码里写注释,告诉编译器:“这段代码只在微信小程序环境下生效”或“这段代码只在 APP 环境下生效”。
语法格式
// #ifdef MP-WEIXIN
console.log('这是在微信小程序中运行的代码');
// #endif
// #ifdef APP-PLUS
console.log('这是在 APP 中运行的代码');
// #endif
// #ifdef H5
console.log('这是在 H5 中运行的代码');
// #endif
同样的,CSS 和 Template 也可以条件编译:
/* #ifdef MP-WEIXIN */
.header {
padding-top: 20px; /* 微信可能需要额外 padding 避开刘海屏 */
}
/* #endif */
<!-- #ifdef APP-PLUS -->
<view class="app-only-component">这是只有 APP 才能看到的组件</view>
<!-- #endif -->
实战场景:
比如你要调用相机。在 H5 中,你可以用 <input type="file" capture="camera">;在微信小程序中,你用 wx.chooseMedia;在 APP 中,你用 plus.camera。你可以把这些不同的实现放在同一个方法里,通过条件编译分发,对外暴露统一的接口。
第五步:从模拟器到真机,再到云端打包
代码写完了,跑得也欢了,怎么给别人用?
1. 真机调试
在 HBuilderX 中,连接你的手机(开启 USB 调试),然后点击 运行 -> 运行到手机或模拟器 -> 选择你的设备。
- 微信小程序:需要在微信开发者工具中配置“本地设置”->“不校验合法域名…”,否则请求会失败。
- APP:首次运行需要在手机上安装一个“DCloud 调试基座”。后续如果想用正式包调试,可以在
manifest.json中配置“App 模块配置”,勾选需要的模块(如定位、分享等),然后重新打包基座。
2. 云端打包(无需安装证书)
这是新手的最爱。你不需要自己去申请 iOS 证书、Android 签名文件。
- 在 HBuilderX 中,点击
发行->原生 App-云打包。 - 选择打包模式(开发版/测试版/正式版)。
- 填写应用名称、图标、启动图。
- 点击“开始打包”。
- 去 DCloud 的云平台控制台查看进度。打包完成后,你会得到一个
.apk(Android) 或.ipa(iOS) 文件,或者直接生成一个二维码,扫码即可下载安装。
注意:iOS 打包需要 Apple Developer 账号,且有效期一年,需要续费。Android 则相对自由。
3. 小程序发布
- 在微信公众平台(mp.weixin.qq.com)注册小程序账号,获取 AppID。
- 在 HBuilderX 的
manifest.json中,填入这个 AppID。 - 点击
发行->小程序-微信。 - HBuilderX 会自动调用微信开发者工具,上传代码包。
- 在微信公众平台后台,提交审核,审核通过后发布。
第六步:进阶之路,如何让你的 App 更专业?
刚才我们只是搭了个架子。要想真正上线,还有很多细节要做。
1. 引入第三方插件
UniApp 有一个庞大的插件市场(ext.dcloud.net.cn)。 比如,你想做一个轮播图,不用自己写 CSS 动画,直接搜“Swiper”,找一个高星插件,导入项目,引用即可。
import Swiper from '@/components/swiper/swiper.vue';
这能节省你 80% 的基础开发时间。
2. 状态管理(Pinia/Vuex)
如果你的应用比较复杂,页面之间要传参,或者全局要保存用户信息(Token、昵称),你需要状态管理。
UniApp 推荐使用 Pinia(Vue3 官方推荐)或 Vuex。
在 main.js 中引入:
import { createPinia } from 'pinia'
const pinia = createPinia()
app.use(pinia)
然后在 store/user.js 中定义状态,在任何页面都可以 useUserStore() 来获取数据。
3. 网络请求封装
不要直接在页面里写 uni.request。封装一个 utils/request.js:
- 统一处理 BaseURL。
- 统一拦截器:在请求头带上 Token,在响应拦截器里判断 401 未登录跳转登录页。
- 统一错误处理:给用户友好的 Toast 提示。
// utils/request.js
export default function request(url, method, data) {
return new Promise((resolve, reject) => {
uni.request({
url: 'https://your-api.com' + url,
method,
data,
header: {
'Authorization': 'Bearer ' + uni.getStorageSync('token')
},
success: (res) => {
if (res.statusCode === 200) {
resolve(res.data);
} else {
uni.showToast({ title: '请求失败', icon: 'none' });
reject(res);
}
},
fail: (err) => {
uni.showToast({ title: '网络异常', icon: 'none' });
reject(err);
}
});
});
}
写在最后:保持热爱,持续迭代
到这里,你已经从一个小白变成了能独立开发多端应用的开发者。UniApp 的魅力在于它的高效和生态。
但请记住,工具只是工具。
- 多端兼容的陷阱:虽然 UniApp 屏蔽了很多差异,但在极端情况下(比如复杂的动画、特定的硬件调用),你还是需要深入研究各端的文档。
- 用户体验至上:在 H5 上流畅的动画,在低配 Android 手机上可能会卡顿。记得做好性能优化,比如图片懒加载、分包加载(在
pages.json中配置subPackages)。 - 持续学习:Vue3 的组合式 API、TypeScript 的支持、UniApp Cloud 云开发,这些新技术层出不穷。
现在,打开你的 HBuilderX,创建一个新项目,试着把你脑海中的那个小想法实现出来吧。不管是做个记账本、找个伴儿、还是做个简单的企业展示页,迈出第一步,你就已经超越了 90% 只停留在“想看教程”阶段的人。
如果在开发过程中遇到报错,别急着百度乱搜,先看控制台日志,再看官方文档(uniapp.dcloud.io),那里有你想要的一切答案。
祝你开发愉快,早日上架!如果有具体的代码问题,随时回来问我,我一直都在。
