简介这是一份《uniapp从入门到精通》完整教程文档专为希望掌握uni-app跨平台开发的初中级开发者打造。文档从环境搭建起步涵盖HBuilderX与微信开发者工具的配置、项目创建与多端运行并深入项目目录结构、开发规范、pages.json全局配置、tabBar设置、组件使用、页面样式布局、Vue基础语法、生命周期、下拉刷新、上拉加载、网络请求封装、数据缓存及图片上传预览等核心环节内容循序渐进实操性强。资源包内为单个docx文档大小5.98MB正文按16个章节组织图文与代码示例结合便于对照练习。相比碎片化教程这份整理好的笔记能帮助开发者系统梳理uni-app知识体系快速定位常见问题。目前已有573人学习使用适合作为入门自学或项目开发中的速查资料。1. 从一套代码多端跑说起uniapp 到底帮你省了什么早些年接跨端需求最稳妥的办法就是同一套业务逻辑写三遍小程序一遍、H5 一遍、App 一遍。维护成本翻三倍改一次需求要拉三端角色对齐两个迭代团队就疲了。后来我把项目迁到 uniapp——基于 Vue 的跨端开发框架一套代码可编译到 iOS/Android App、H5 和各小程序平台重复劳动才真正降下来。这篇笔记围绕 uniapp 从入门到精通的完整落地路径展开环境怎么搭、页面路由怎么配、网络层与状态管理怎么设计以及不踩一遍根本记不住的跨端坑。适合两类读者刚接触跨端开发、想找可靠路线的初学者已经在用 uniapp 但被平台差异折磨、想系统排查问题的工程师。全文以可复现的命令、配置和代码为主不堆概念也不劝退——你只需要把每一章的清单对照自己的项目过一遍。2. 先把开发环境跑通HBuilderX 与 CLI 两种初始化路线怎么选跨端项目动工前的第一件事不是写页面而是选初始化路线。常见做法有两条直接用官方 IDE HBuilderX 图形化创建或者用命令行工具按 Vite 模板初始化工程。两条路都能跑但后续的构建方式、版本管理、团队协作体验完全不同选错再迁是要掉头发的。2.1 HBuilderX 图形化创建下载、新建项目与真机运行HBuilderX 是 uniapp 配套的集成开发环境内置了编译、预览、真机调试能力新手半小时内就能看到第一屏。操作路径如下下载 HBuilderX 并解压Windows 下安装路径不要带中文和空格否则部分插件会报错。打开后依次点击「文件 → 新建 → 项目」弹出模板选择框选择 uniapp 项目框架版本选 Vue 3。创建完成后按 CtrlR选择「运行到浏览器」默认会启动本地开发服务并打开预览页。如果要跑小程序先在菜单「工具 → 设置 → 运行配置」里指定小程序开发者工具的安装路径再选择「运行到小程序模拟器」。这套流程最大的价值是零配置不需要手动安装 Node 依赖编译器已经内置在 IDE 里。缺点是工程里的依赖版本跟随 IDE 版本走团队协作时容易出现「我这边编译正常、你那边报错」的情况本质上是 IDE 版本不一致导致的。临时验证想法、个人练手用 HBuilderX 完全够但中大型项目建议直接看下一条。2.2 CLI 路线用 Vite 模板初始化 uniapp 工程的最小命令CLI 路线把 uniapp 纳入标准 Node 工程体系依赖由 package.json 锁定后续接持续集成、自动化测试都顺理成章。先跑通最小命令# 用 degit 拉取官方 Vite 模板my-uniapp-app 换成你自己的项目名 npx degit dcloudio/uni-preset-vue#vite my-uniapp-app cd my-uniapp-app # 安装依赖国内网络慢可以先用 registry 镜像 npm install # 启动 H5 端开发服务浏览器访问 localhost:5173 实时预览 npm run dev:h5degit 的作用是拉取 GitHub 仓库的快照不带 git 历史比 clone 更干净。模板名后面的#vite指定的是 Vue 3 Vite 分支如果你项目还在用 Vue 2这里要换成对应的 Vue 2 模板分支。npm run dev:h5对应的是 package.json 里 scripts 配置具体编译命令如下{ scripts: { dev:h5: uni, build:h5: uni build, dev:mp-weixin: uni -p mp-weixin, build:mp-weixin: uni build -p mp-weixin, dev:app: uni -p app, build:app: uni build -p app } }-p参数指定编译目标平台mp-weixin表示微信小程序。编译产物默认输出到dist/dev/mp-weixin目录用微信开发者工具「导入项目」选中这个目录即可预览。注意 App 端的dev:app只是生成原生工程资源真机运行仍然依赖 HBuilderX 的云打包或本地打基座CLI 不能完全替代 IDE 这一步。2.3 两条路线怎么选版本管理、团队协作与打包差异选型没有绝对答案取决于你的交付场景。下面这张表可以帮你快速决策维度HBuilderXCLI上手成本低开箱即用中需要 Node 环境版本管理依赖跟随 IDE团队难统一package-lock.json 锁版本一致性好小程序联调内置自动唤起开发者工具编译后手动导入 dist 目录App 打包直接云打包仍要借助 HBuilderX 或原生工程适合场景个人原型、快速验证中大型团队、标准化工程我一般会建议团队项目直接走 CLIHBuilderX 留给 App 云打包这一个环节。理由不复杂CLI 工程的依赖、构建脚本、环境变量都能写进仓库新人拉下来npm install npm run dev:h5就能跑而 HBuilderX 工程拷贝给同事后经常因为 IDE 版本不同出现编译差异排查半天发现是工具链问题纯属浪费时间。注意CLI 初始化后如果提交代码到仓库务必先提交 package-lock.json并确认node_modules已被 .gitignore 排除。锁文件丢失是团队依赖不一致的第一大原因。3. 页面与路由pages.json 配置和生命周期序列先立住uniapp 的路由模型继承自小程序所有页面必须先在 pages.json 里注册导航栏、tabBar、全局样式也都在这个文件里声明。新手最容易在这一步翻车——漏注册页面导致跳转黑屏或者 tabBar 路径少写一个字母导致编译报错。3.1 pages.json页面注册、全局样式与 tabBar 配置要点pages.json 是路由和页面配置的总入口一个最小可用示例长这样{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 首页, enablePullDownRefresh: true } }, { path: pages/my/my, style: { navigationBarTitleText: 我的 } } ], globalStyle: { navigationBarTextStyle: white, navigationBarBackgroundColor: #1989fa, backgroundColor: #f6f6f6 }, tabBar: { color: #999999, selectedColor: #1989fa, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/my/my, text: 我的 } ] } }pages数组的第一项是启动页决定 App 冷启动和小程序首屏进入哪个页面。每项的path从项目根目录算起不需要带斜杠前缀。style里的navigationBarTitleText对应导航栏标题enablePullDownRefresh开启下拉刷新后页面还需要在 JS 里配合onPullDownRefresh生命周期。tabBar.list最少 2 项、最多 5 项pagePath必须与pages里注册的路径完全一致且 tabBar 页面不支持通过 URL 带参数访问。3.2 页面生命周期onLoad、onShow、onReady 的执行顺序与常见误用页面生命周期是 uniapp 里最容易被误用的地方。很多开发者习惯把所有初始化逻辑塞进 onLoad结果从二级页面返回时数据不刷新又回来查半天。把时序记清楚能省掉这类问题生命周期触发时机执行次数典型用途onLoad页面创建时1 次读取路由参数、初始化数据onShow页面每次显示时多次刷新列表、埋点上报onReady首帧渲染完成时1 次操作节点、初始化地图onHide页面隐藏时多次暂停轮询、清理计时器onUnload页面销毁时1 次移除全局事件监听顺序上首次进入一个页面onLoad → onShow → onReady 依次执行。但从详情页返回列表页时列表页只会触发 onShow不会再走 onLoad。常见的误用场景是「购物车页面修改数量后返回首页首页总数没变」原因就是刷新逻辑写在 onLoad 里。正确做法是 onLoad 拉首屏onShow 里做增量刷新并加一个标志位避免首次进入时重复请求。3.3 路由跳转三兄弟navigateTo、redirectTo、switchTab 怎么选uniapp 提供了三个常用的跳转 API各有各的适用场景选错的表现通常是页面栈爆炸或参数丢失// 压栈跳转目标页入栈当前页保留在栈中可以返回上一页 uni.navigateTo({ url: /pages/detail/detail?id123fromhome }) // 重定向关闭当前页再打开目标页登录页跳首页这类场景常用 uni.redirectTo({ url: /pages/login/login }) // 切换到 tabBar 页面只能用这个 API且 URL 不能携带自定义参数 uni.switchTab({ url: /pages/index/index })navigateTo 每次跳转都会往页面栈里压一层连续跳五六层后栈会溢出这时手势返回会失效应改用 redirectTo 替换掉不需要返回的中间页。参数传递走 URL query对象类型参数必须JSON.stringify后再encodeURIComponent否则接收端拿到的是字符串[object Object]这是最常见的玄学问题之一。跨页面传大对象或需要回调时建议用uni.$emit/uni.$on或 eventChannel不要在 URL 里塞长字符串。4. 网络层与状态管理封装 uni.request 和 Pinia 落地的细节页面能跑起来只是开始真正决定跨端项目工程质量的是网络层和状态管理。如果每个页面直接调 uni.request接口地址散落各处、错误处理五花八门后期改一个 baseURL 要全局搜索。更关键的是App、小程序、H5 三端的请求行为有差异必须提前收口。4.1 封装一个带拦截器、超时与错误归一的请求模块我一般会把请求封装成一个 Promise 函数统一处理域名拼接、token 注入、超时和错误提示。最小可用的封装如下// utils/request.js —— uniapp 请求统一封装 const BASE_URL https://api.example.com // 按环境切换dev/test/prod export function request(options) { return new Promise((resolve, reject) { const token uni.getStorageSync(token) uni.request({ url: BASE_URL options.url, method: options.method || GET, data: options.data || {}, timeout: options.timeout || 15000, // 默认 15 秒超时 header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, success: (res) { // 约定后端返回 { code, data, message }code 为 0 表示成功 if (res.statusCode 200 res.data.code 0) { resolve(res.data.data) } else { uni.showToast({ title: res.data.message || 请求失败, icon: none }) reject(res.data) } }, fail: (err) { // 网络不通、超时统一走这里 uni.showToast({ title: 网络异常请稍后再试, icon: none }) reject(err) } }) }) }封装的价值在于把平台差异收在函数内部页面里只关心业务数据不关心 token 怎么塞、超时怎么配。注意timeout参数在 H5 端由浏览器控制小程序端还会受 manifest 里mp-weixin节点的全局networkTimeout影响两端行为不完全一致。header 里的Authorization在 H5 端如果触发跨域预检后端需要正确响应 OPTIONS 请求否则请求到不了业务层。4.2 状态管理怎么选Vuex 还是 Piniauniapp 里怎么接状态管理选型要看你的 uniapp 版本Vue 2 工程基本只能稳定使用 VuexVue 3 工程建议直接上 PiniaAPI 更简洁TypeScript 支持也好。Pinia 接入 uniapp 和普通 Vue 项目略有差别因为 uniapp 的入口不是传统new Vue().$mount()而是导出createApp工厂函数// main.js —— Vue 3 版本必须用 createSSRApp 创建实例 import { createSSRApp } from vue import * as Pinia from pinia import App from ./App.vue export function createApp() { const app createSSRApp(App) app.use(Pinia.createPinia()) return { app, Pinia } }// stores/user.js —— 用户态示例 import { defineStore } from pinia export const useUserStore defineStore(user, { state: () ({ token: , userInfo: null }), actions: { setLogin(token, userInfo) { this.token token this.userInfo userInfo uni.setStorageSync(token, token) // 同步写缓存App 冷启动可恢复 }, logout() { this.token this.userInfo null uni.removeStorageSync(token) } } })createApp必须返回{ app, Pinia }这个结构原因是 uniapp 在 App 端和小程序端需要在运行时访问 Pinia 实例完成依赖注入。页面里调用useUserStore()时Pinia 会从 app 上下文取实例所以不要在 store 定义文件里直接useUserStore()再导出那会在实例创建前执行并报错——这是踩过一次的人才会注意到的细节。token 同时写 store 和 storage是为了覆盖「用户冷启动 App」的场景store 内存态能保证页面刷新后立即可用storage 负责持久化恢复。4.3 登录态与 token 刷新并发请求去重的两种常见做法业务里最怕的场景是token 过期瞬间页面同时发 5 个请求全部返回 401于是每个请求各自去调一次刷新 token 的接口刷新请求被并发轰炸。解决思路是让所有 401 共享同一个刷新 Promise// utils/refresh.js —— token 刷新去重同时到达的 401 只发一次刷新请求 let refreshing false let waitQueue [] function handleTokenExpired(retryRequest) { // 已有刷新请求在飞后续请求先挂到队列等待 if (refreshing) { return new Promise((resolve) waitQueue.push(resolve)) } refreshing true return refreshTokenApi() .then((newToken) { uni.setStorageSync(token, newToken) // 统一唤醒所有等待中的请求让它们用新 token 重放 waitQueue.forEach((resolve) resolve()) waitQueue [] }) .finally(() { refreshing false }) .then(() retryRequest()) }这个模式的本质是「把第一个 401 当作刷新信号其余请求当乘客」。实现上有两个注意点一是刷新完成后要更新请求 header 里的 Authorization 再重放否则会二次 401 进入死循环二是刷新接口自身如果也返回 401要在这里做兜底清理登录态并跳转登录页否则队列永远挂起。5. uniapp 跨端避坑指南5 个高频故障的排查路径uniapp 的文档其实不薄但很多坑是文档写了、你没到真机那一步根本注意不到。下面这 5 个问题是我和身边同事反复遇到的高频故障按「现象 → 原因 → 解决」逐一拆开。5.1 图片在 App 端不显示H5 却正常现象同一张图片H5 和小程序都正常一跑 App 真机就裂开控制台没有任何报错。原因排查有三层第一图片路径用了相对路径或动态拼接App 端对相对路径的解析与 H5 不同编译后资源找不到第二图片放在src/assets下而非static目录App 端静态资源只认后者第三远程图片的 CDN 域名未配置到 App 的合法域名白名单。解决静态图片一律放到static目录并用绝对路径/static/xxx.png引用远程图片确认 URL 是完整https://开头并在 manifest.json 的 App 节点配置网络域名。排查时先在浏览器直接打开图片 URL排除了资源本身的问题后再往路径上怀疑。5.2 onLoad 里拿到的路由参数是 undefined 或 [object Object]现象跳转时明明在 url 里拼了参数接收页onLoad(options)打印却是 undefined或者对象参数变成了字符串[object Object]。原因uniapp 的onLoad收到的参数是经过 decode 的 query 对象但对象类型的参数没有序列化就直接拼进 url会被强制转成字符串。另一个常见操作错误是用switchTab跳转 tabBar 页并带参数tabBar 跳转会直接丢弃 query。解决对象参数先encodeURIComponent(JSON.stringify(obj))再拼进 url接收页用JSON.parse(decodeURIComponent(options.obj))还原。需要给 tabBar 页面传参时改用uni.setStorageSync配合onShow读取或者写进 Pinia store——不要在 switchTab 的 url 上做任何期望。5.3 H5 端请求被 CORS 拦下现象本地npm run dev:h5跑起来接口请求在浏览器控制台报blocked by CORS policy后端说已经允许跨域了。原因本地开发服务器默认端口如 5173与后端接口域名不一致浏览器遵守同源策略拒绝读取响应。后端允许跨域只能解决生产环境开发环境的请求是从 localhost 发出的预检请求经常到不了后端或者响应头配置有误。解决在 manifest.json 的h5节点配置开发代理让本地请求走同源转发{ h5: { devServer: { proxy: { /api: { target: https://backend.example.com, changeOrigin: true, pathRewrite: { ^/api: } } } } } }配置后前端请求/api/user/info会被代理到后端地址浏览器看到的是同源请求跨域消失。注意pathRewrite是否启用取决于后端接口是否带/api前缀带的话就不需要重写。生产环境如果还跨域那是后端 CORS 配置问题和前端代理无关。5.4 App 冷启动白屏现象App 装到真机上冷启动白屏但 HBuilderX 内置浏览器和模拟器都正常。原因最常见的是代码里用了 H5 独占的 API如window、document或依赖了浏览器特性的库App 端 webview 环境不支持导致首屏 JS 报错中断渲染。第二个常见原因是 tabBar 页面里混合使用了原生组件与普通组件原生组件层级特殊渲染时序不对就会盖住白屏。解决凡是依赖浏览器 API 的逻辑用条件编译隔离在#ifdef H5块里App 端不编译。排查步骤是先看 App 端控制台有没有 JS 报错有报错就先修报错没有报错再看入口页面是否引用了地图、视频等原生组件尝试把它们从首屏移除验证。白屏问题七成是「某个 API 在 App 端不存在」剩下三成是原生组件渲染时序。5.5 scroll-view 在 iOS 上滚动失效现象页面里用了scroll-view做纵向滚动Android 正常iOS 上滚动不跟手或者内容超出容器后根本滚不动。原因scroll-view需要明确的高度才有滚动容器iOS 上如果父容器高度依赖内容撑开滚动高度计算为 0自然滚不动。另一个常见诱因是页面本身开启了滚动与scroll-view的滚动形成嵌套冲突iOS 的橡皮筋效果会进一步放大问题。解决给scroll-view设置固定高度如height: calc(100vh - 系统导航栏高度 - 自定义头部高度)并确认样式上设置了overflow行为正常。如果页面只需要整体滚动就不要用scroll-view直接让页面级滚动生效必须嵌套时外侧页面关闭滚动由内层scroll-view独占。排查时先在 iOS Safari 里直接打开 H5 版本复现能更快定位是编译问题还是原生滚动冲突。6. 从调通到好用交付前必查的性能指标与优化手法功能全跑通之后离上线还差一步性能验收。跨端项目最常见的验收翻车是「模拟器流畅、真机卡顿」和「小程序包体积超限」。我给自己定了几条硬指标每次交付前对照检查一遍。小程序端优先做分包。微信小程序单包超过 2MB 就无法上传分包是唯一出路。在 pages.json 里按业务域拆分把低频页面放副包{ subPackages: [ { root: pages/order, pages: [list/list, detail/detail] } ] }主包只保留 tabBar 页面和公共依赖分包按需加载首包体积能砍掉一半以上。App 端则重点看首屏首页不要串行请求三条以上接口改用Promise.all并行白屏时间能降一个量级。另外项目里如果引用了只在某个平台使用的重型库记得用条件编译隔离别让 App 端打包无关代码。检查项建议目标常用手段小程序主包体积小于 1.5MB分包、按需引入组件库首屏接口耗时全部并行无串行Promise.all 并发App 冷启动白屏3 秒内出现首帧移除首屏原生组件、压缩首屏数据图片体积单张小于 200KBCDN 压缩、WebP 格式我自己早期一个跨端项目上线前只顾着调功能漏了包体积检查结果小程序上传时超限加班拆包拆到凌晨。从那以后我养成了习惯每次提测前先跑一次生产构建把各端包体积和首屏请求数量写进验收清单这一步省下的时间远比花掉的多。性能优化没有一招鲜但把指标定清楚、把检查动作沉淀成习惯比迷信任何优化技巧都可靠。真机验证是最后一道防线。模拟器里正常不等于真机正常尤其是图片路径、键盘弹起、原生组件渲染这三类问题必须用真机过一遍。希望帮到你。本文还有配套的精品资源点击获取