ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

uni-app跨端开发实战:HBuilderX+Vue3从零搭建全流程

uni-app跨端开发实战:HBuilderX+Vue3从零搭建全流程 做跨端开发这几年我见过太多人一上来就对着 uni-app 的文档硬啃啃了两周还在问“pages.json 到底干嘛的”。这篇教程我打算换一个讲法不按文档顺序念经而是从零开始带着你一步一步把 uni-app 跑起来把页面、路由、语法、请求、调试这些核心环节全部走一遍。内容会偏向 HBuilderX Vue 3 这套组合因为这是目前新手起步最顺、资料最全的路线。无论你是刚入行前端的小白还是已经会 Vue 但没接触过跨端开发的老手都可以跟着这篇文章操作。第一篇先把地基打牢把“怎么开发 uni-app 程序”这件事彻底搞清楚。1. 准备工作先把 HBuilderX 装明白很多人喜欢用 VSCode 写前端但做起 uni-app 来我还是推荐直接用 DCloud 官方的 HBuilderX。不是说 VSCode 不行而是 HBuilderX 对 uni-app 做了深度定制内置了模拟器、真机同步、小程序调试的一整套链路甚至连新建项目这种操作都替你考虑好了模板。你用 VSCode 还得自己配一堆插件和命令新手很容易在这上面卡半个月。所以第一课先把 HBuilderX 装好把项目的根目录结构弄清楚。1.1 为什么要用 HBuilderX 而不是 VSCodeHBuilderX 本质上是一个强化版编辑器它对 uni-app 的支持是“开箱即用”的。你下载安装之后新建项目时直接能看到 uni-app 的模板选项运行时一键就能跑到手机或小程序开发者工具里。相比之下VSCode 需要你手动安装 uni-app 相关的插件还要通过命令行创建项目、配置环境变量对新手来说繁琐且容易出错。下载的时候记得去官网下载正式版别用 Alpha 版。Alpha 版虽然是新功能试验场但稳定性差一些新手踩到 bug 容易分不清是代码问题还是工具问题。安装路径也不要带中文和空格否则后面运行小程序时会莫名其妙报错这个坑我见过太多次了。1.2 新建项目的完整流程打开 HBuilderX点击左上角的“文件 - 新建 - 项目”在弹出的窗口里选择uni-app分类。这里要重点说一下模板的选择默认模板只包含最基本的骨架干净、没有多余代码最适合学习阶段使用如果你看到有uni-ui或uview-plus这类带 UI 库的模板先别急着选等掌握了基础写法再引入也不迟。版本选择上我强烈建议选择 Vue 3。现在的 HBuilderX 新版对 Vue 3 支持已经非常成熟而且 Vue 3 的组合式 API 写起来比 Vue 2 的选项式 API 舒服太多。选好 Vue 3 之后输入项目名称例如my-first-app点击创建。一个干净的 uni-app 项目就诞生了。1.3 目录结构和文件职责解读创建完项目后你会看到左侧资源管理器里有几个预置目录和文件。很多新手上来就写代码从不停下来看这些文件是干嘛的结果后面一报错就懵。我建议你把它们当成一个团队来认识文件/目录职责pages目录存放所有页面每个页面通常由.vue文件组成static目录放静态资源比如图片、字体文件这里面的文件不会被编译处理App.vue应用入口组件全局生命周期写在这里相当于整个应用的根main.js创建应用实例的地方Vue 3 模式下主要是createSSRAppmanifest.json配置应用名称、图标、权限、SDK 等关键信息打包时它就是“搬家清单”pages.json最重要的配置文件所有页面的路由、导航栏、tabBar、样式都在这里声明uni.scss全局样式变量文件可以写一些复用颜色、间距提示uni-app 的页面路由和传统 Vue 项目不一样它不是靠目录自动生成的而是在pages.json里手动注册的。你在pages目录里新建了一个页面如果不在pages.json里登记项目根本不会识别它。这是新手最常犯的错误之一。2. 页面与路由整个应用的骨架搞清楚目录结构之后下一步就是把页面串起来。uni-app 的多端路由核心都集中在pages.json这一个文件里看起来简单但里面藏了很多细节。理解了这章内容你就不会再出现“页面跳转失灵”或者“tabBar 不显示”这类低级问题了。2.1 pages.json 是页面总开关打开项目的pages.json默认会有一个pages数组里面第一项就是首页。这个数组很重要数组的第一项永远是你的默认启动页。你后续每新建一个页面都要在这个数组里加一条记录比如{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 首页 } }, { path: pages/detail/detail, style: { navigationBarTitleText: 详情页 } } ], globalStyle: { navigationBarTextStyle: black, navigationBarTitleText: uni-app 教程, navigationBarBackgroundColor: #F8F8F8, backgroundColor: #F8F8F8 } }path指向pages目录下的具体页面路径style里可以单独配置每个页面的导航栏标题和背景色。如果你希望某个页面隐藏导航栏把navigationStyle设为custom即可。globalStyle则是全局兜底配置所有页面没有单独声明时都会继承这里的内容。另外如果你要做底部导航栏tabBar也写在pages.json里。它支持 2 到 5 个list项每项至少包含pagePath和text图标可以用iconPath和selectedIconPath指定。需要注意的是tabBar 里的页面必须已经在pages数组中注册过且图片建议放在static目录下尺寸控制在 81px * 81px 左右否则容易压缩变形。2.2 页面跳转的几种常用方式uni-app 的页面跳转 API 一共有四个uni.navigateTo、uni.redirectTo、uni.switchTab、uni.reLaunch。它们的区别简单说就是navigateTo跳转非 tabBar 页面最常用会保留当前页面可以返回。redirectTo也跳非 tabBar 页面但会关闭当前页面不保留通常用于登录态校验后的重定向。switchTab专门用于跳转 tabBar 页面比如你要从某个普通页面切回首页。reLaunch可以关闭所有页面重新打开一个新页面适合做退出登录这类场景。跳转时带参数直接写在url后面用?拼接uni.navigateTo({ url: /pages/detail/detail?id1001titlehello });接收参数在目标页面的onLoad生命周期里取onLoad(options) { console.log(options.id, options.title); }这里有个隐藏细节如果参数里有中文或特殊字符直接拼接会导致解码乱码或截断。你在项目中凡是遇到这类参数都要先encodeURIComponent编码接收时用decodeURIComponent解码。这已经是跨端开发的老生常谈了但每次面试和实战中都有人踩。2.3 生命周期知道什么时候干什么事uni-app 的生命周期分为三层应用生命周期、页面生命周期、组件生命周期。刚入门的同学最容易混淆的是应用级和页面级的区别。应用级生命周期写在App.vue里最常用的是onLaunch它在应用初始化完成时触发一次一般在这里处理全局登录状态检查、全局数据预加载。页面级生命周期写在每个页面的.vue文件中onLoad在页面初次加载时触发适合接收入参并请求首屏数据onShow在页面每次显示时触发适合做列表刷新onReady在页面首次渲染完成时触发适合操作 DOM 或获取节点信息onHide和onUnload分别在页面隐藏和销毁时触发适合清理定时器和监听器。组件生命周期则和 Vue 一致在 Vue 3 里是onMounted、onUnmounted这类写法。理解了这套触发顺序你写数据刷新逻辑的时候就能选对地方不会再出现“在 onLoad 里反复请求导致性能差”的问题。3. 语法与状态从 Vue 顺利切入 uni-appuni-app 的页面文件后缀是.vue所以写页面本质上就是写 Vue 组件。Vue 3 的组合式 API 是当前主流uni-app 也提供了完整支持。这一章我会用一个小例子把模板语法、事件绑定和自动导入ref这几个重点一次性讲清楚。3.1 模板语法与数据绑定页面的template区域写结构script setup区域写逻辑。Vue 的指令在 uni-app 里都通用v-for循环列表、v-if条件渲染、:class动态绑定样式、{{ }}插值输出文本。这里贴一个最简单的计数器页面template view classcontainer text classcount{{ count }}/text button tapincrement增加/button /view /template script setup const count ref(0); function increment() { count.value; } /script注意一点uni-app 的事件名和 Web 略有不同。在 H5 端你可以用click但在小程序端tap才是统一规范的写法。为了跨端一致我建议统一使用tap。按钮上的事件在原生 App 上也能稳定触发不用为不同端写两套。3.2 自动导入 refVue 3 开发效率翻倍的关键既然说到了script setup就绕不开ref。在传统的 Vue 3 写法和大部分网络教程里你必须在script里写import { ref } from vue然后才能用ref创建响应式数据否则控制台直接给你抛一个ref is not defined。但是很多从 HBuilderX 新建的 uni-app 项目里你可能会发现不写import也能直接用ref这就是它内置的“Vue API 自动导入”机制。HBuilderX 创建的 Vue 3 项目已经在编译层面做了处理它会自动把vue模块里的常用 APIref、computed、watch等注入到页面作用域中省去手动 import 的重复劳动。如果你是使用 CLI 方式创建的 uni-app 项目或者遇到了不自动导入的情况可以通过 Vite 插件unplugin-auto-import来实现。在vite.config.js中做如下配置import { defineConfig } from vite; import uni from dcloudio/vite-plugin-uni; import AutoImport from unplugin-auto-import/vite; export default defineConfig({ plugins: [ uni(), AutoImport({ imports: [vue] }) ] });配置完之后重启开发服务器刷新页面你就可以放心地在任何页面里直接使用ref、computed、watch不再需要手写 import。注意自动导入听起来很爽但有一个小坑——它会隐式引入依赖。如果其他没配置这个机制的人接手你的项目他看到ref就懵了不知道这个变量从哪来的。所以你在团队协作时要提前说清楚或者干脆在文件顶部保留显式导入让代码可读性更强。个人项目或者调试阶段自动导入能省不少事。3.3 事件处理与组件通信父子组件通信的写法在 uni-app 中与 Vue 保持高度一致。父组件通过属性传值子组件通过emit抛事件。子组件里定义propstemplate view text{{ title }}/text button taphandleTap点击我/button /view /template script setup const props defineProps({ title: String }); const emit defineEmits([customEvent]); function handleTap() { emit(customEvent, 来自子组件的数据); } /script父组件调用时这样写template child-component :titlepageTitle customEventonCustomEvent / /template script setup const pageTitle ref(子组件标题); function onCustomEvent(data) { console.log(data); } /script这种通信方式已经能覆盖绝大多数业务场景。如果你发现跨层级的组件通信写起来很痛苦那就该考虑引入状态管理工具了下一章我会专门展开讲。4. 常见需求落地请求、存储与状态管理一个真实的 uni-app 项目光有页面是不够的还得能请求后端数据、缓存用户登录态、管理共享状态。这些功能每一个都有官方 API但直接裸用会写出一堆重复代码。这一章我分享一套我自己项目里一直在用的封装思路。4.1 封装 uni.request一次性解决重复代码uni.request是 uni-app 里发起网络请求的底层 API。它的写法本身没问题但如果你在十个页面里复制粘贴同样的uni.request({ url: ... })后面改 baseURL 或者加 token 时就会遍体伤痕。我推荐封装一个统一的request函数比如在utils/request.js里写const BASE_URL https://api.example.com; export function request(options) { return new Promise((resolve, reject) { uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, header: { Content-Type: application/json, Authorization: uni.getStorageSync(token) || }, success: (res) { if (res.statusCode 200 res.statusCode 300) { resolve(res.data); } else if (res.statusCode 401) { uni.showToast({ title: 登录已过期, icon: none }); // 这里可以做重定向到登录页 } else { uni.showToast({ title: res.data.msg || 请求失败, icon: none }); reject(res); } }, fail: (err) { uni.showToast({ title: 网络异常, icon: none }); reject(err); } }); }); }这样封装之后业务页面里请求数据就变成了const list ref([]); async function fetchData() { const res await request({ url: /api/list, method: GET }); list.value res.list; }代码立刻清爽了很多。token 的读取和错误处理也统一了后面升级拦截逻辑只需要改一个文件。4.2 本地缓存与登录态管理uni.setStorageSync和uni.getStorageSync是同步的本地读写方法适合存登录 token、用户信息这类小数据。在success回调里拿到登录接口的 token 后直接存入本地uni.setStorageSync(token, res.token); uni.setStorageSync(userInfo, res.userInfo);退出登录时清掉这些数据uni.removeStorageSync(token); uni.removeStorageSync(userInfo);提示同步方法虽然写着方便但在数据量大或者执行频繁的情况下会阻塞线程。对于用户头像、日志列表这类体积比较大的数据建议换成异步的uni.setStorage和uni.getStorage。4.3 状态管理用 Pinia 还是 VuexVue 2 时代用 Vuex 是常规操作但 Vue 3 时代我更推荐 Pinia。Pinia 的 API 更简洁没有 mutation类型推导也更好在 uni-app 的 Vue 3 项目里用起来非常顺手。你可以在官方插件市场找到对应的安装配置。确定使用哪个方案主要看项目的规模和团队习惯。如果你只是存储一个登录态和购物车数量那完全不需要引入任何状态管理库用uni.setStorageSync加一个简单的响应式对象就够了当你的业务有大量跨页面共享、实时更新的数据时才上 Pinia 这类工具。我见过很多人一上来就全局引入 Vuex项目跑起来后 90% 的状态都是写进不读出的死代码这种过度设计完全没有必要。5. 调试与真机运行开发体验的关键很多新手写完代码点击运行看到浏览器里页面出来了就开始欢呼。实际上uni-app 的完整调试链路不止在浏览器里跑通那么简单。真正上线前的真机测试、小程序调试工具联动才是这章要讲的重点。5.1 在 HBuilderX 里使用调试工具HBuilderX 运行到浏览器后页面会在 Chrome 中打开。你可以在浏览器里按 F12 打开开发者工具查看 console 日志、network 请求、页面元素。如果你用了 Vue 3可以安装 Vue DevTools 浏览器扩展这样在浏览器里能看到组件树和响应式数据的状态排查数据更新问题非常直观。这部分唯一要提醒的是uni-app 的console.log在 H5 端会显示在浏览器控制台在真机上则需要打开 HBuilderX 的“控制台”面板查看。如果你发现真机上看不到日志优先检查是否开启了调试模式以及是否连接了正确的运行设备。5.2 真机运行与微信开发者工具联动HBuilderX 支持直接把项目跑到手机上进行真机调试。点击“运行 - 运行到手机或模拟器”手机通过 USB 连接电脑开启开发者模式中的 USB 调试HBuilderX 就会自动检测到设备并安装调试版应用。如果你开发的是小程序需要先在电脑上安装微信开发者工具然后点击“运行 - 运行到小程序模拟器 - 微信开发者工具”。首次运行会要求在微信开发者工具里开启服务端口设置 - 安全设置 - 服务端口。这样每次改动代码HBuilderX 会自动编译并刷新到小程序模拟器里。在真机或小程序上调试时网络请求常常会遇到“不在以下 request 合法域名列表中”或者“http 不是合法协议”这类提示。这是因为微信小程序要求使用 HTTPS且必须在后台配置合法域名。开发阶段可以在开发者工具里勾选“不校验合法域名”但要上线时记得配置好真实域名否则发布审核会被拒。5.3 条件编译一套代码处理多端差异跨端开发不可避免会遇到平台差异。比如 H5 端可以使用浏览器特有的window对象但 App 和小程序端没有再比如 H5 端分享功能可以调用浏览器原生 API但小程序端只能走小程序的onShareAppMessage。uni-app 提供了条件编译注释来处理这类问题。语法就是在注释里加上#ifdef或#ifndef例如template view !-- #ifdef H5 -- text这是H5端才显示的内容/text !-- #endif -- !-- #ifdef MP-WEIXIN -- text这是微信小程序端才显示的内容/text !-- #endif -- /view /templateJavaScript 里也是一样的写法// #ifdef H5 console.log(H5环境); // #endif条件编译不是运行时判断而是编译期处理。也就是说你打包 H5 时小程序那部分代码会在编译阶段被删除不会给用户造成多余体积。理解这一点后你在写多端差异逻辑时心里就有数了。6. 新手必踩的坑实测问题排查与避坑清单写了三四个项目之后我发现 uni-app 新手遇到的问题高度相似。这里我把高频出现的坑整理成了一个速查表并补充了我自己的排查经验希望能帮你少掉几次头发。6.1 高频报错速查表报错现象原因解决办法pages/index/indexnot found页面没在 pages.json 注册打开 pages.json确认页面路径已加入 pages 数组uni.xxx is not a function使用了一个当前平台不支持的 API查阅官方文档确认 API 在对应端是否有兼容性限制rpx数值不生效CSS 文件里混用了upx或语法错误uni-app 中单位统一用rpx别再写upx图片显示不出来图片路径错误或静态资源在非 static 目录图片放static目录引用时用绝对路径/static/xx.png请求接口报 404/405网络请求域名未配置或接口路径错误检查 baseURL 和后端接口路径小程序需配置合法域名真机预览白屏可能是缓存或代码兼容问题清除缓存重新运行检查控制台有无报错组件不显示easycom 规则不匹配组件名和文件路径符合 easycom 规范组件目录名组件名6.2 样式与尺寸适配心得uni-app 在样式中推荐使用rpx作为尺寸单位它和微信小程序的rpx保持一致的换算逻辑不管屏幕宽度是多少750rpx永远等于屏幕宽度。所以你写375rpx在任何设备上都会占到屏幕一半宽度。这个设计对多端开发非常友好比px和vh稳得多。但需要注意字体大小和边框圆角这类细节我通常还是用px写。因为rpx会根据不同设备屏幕缩放字体如果用rpx在大屏手机上会显得偏大视觉比例反而不协调。另外iPhone 的底部小黑条需要适配安全区域uni-app 支持env(safe-area-inset-bottom)在需要固定底部按钮的页面加上这段 padding就能避开被手势条遮挡的尴尬。6.3 打包发版前的几个检查项准备上线时不要登录账号就点发行先检查这几项manifest.json里应用名称和 appid 是否正确图标和启动图是否已经替换成正式资源。网络请求地址是否已经改成正式环境的 HTTPS 地址。微信小程序后台的服务器域名是否已经添加完毕合法域名必须包含你的接口域名。所有console.log是否已经移除可用console的方式批量注释避免调试数据泄露到线上。是否开启了 uni-app 的代码压缩选项减少包体体积。最后说一个我自己的使用习惯每运行到一个新平台我会先写一个空的页面跑通“环境验证”确认编译链路没问题再开始堆业务代码。这样能把环境问题限制在最小范围内后续定位问题也不会满屏报错看得头疼。这篇教程里讲的内容都是你写任何 uni-app 项目都会反复碰到的知识点先把它们练熟后续我们就能放心去聊 UI 组件封装、自定义原生插件、性能优化这些更进阶的主题了。
返回列表