我入行前端这几年带过团队也带过新人有一个感受特别强烈很多人第一次听说 uni-app第一反应是“这不就是个套壳框架吗”等真正拿它做完一两个跨端项目之后又会对它评价两极分化。其实 uni-app 没那么玄乎它就是一套基于 Vue 的跨端框架写一套代码通过编译器转成小程序、App、H5 甚至快应用都能跑的程序。你只要稍微懂点 Vue基本就能看懂 uni-app 的核心逻辑完全不懂 Vue 也没关系这篇教程就是从零开始讲的。这是整个系列的第一部分。我会从环境搭建、创建项目、目录结构、核心语法、条件编译、HBuilderX 效率配置一直聊到最常见的几个坑位和排查方法。目标只有一个让你跟着操作下来能完整跑起来一个同时输出多端、并且逻辑清晰的真实页面。这篇内容偏基础但里面有不少是我实际踩过坑之后才总结出来的细节哪怕你已经是熟手翻到后面几个问题排查的小节应该也会有点收获。1. 为什么偏偏是 uni-app项目思路拆解1.1 一套代码多端运行的底层逻辑打个生活化的比方传统开发方式下你要分别跟微信团队、App Store、浏览器打交道每个平台都有自己的技术规矩等于每一端都要派一个“翻译”成本自然高。uni-app 的做法是让你用 Vue 的语法来写业务代码然后在编译阶段根据你选择的目标平台生成对应的产物。比如你要做小程序编译器就会把页面转成 wxml、wxss、js你要做 H5就生成标准的网页代码你要做 App则通过内置的 webview 加原生能力支持把它打包成安卓和 iOS 都能安装的应用。这套机制的核心优势不是“完全一致”而是“业务逻辑一次编写、平台差异按需适配”。实际开发中没有哪个项目是真正做到 100% 零改动跑通所有端的但 80% 的页面、组件和接口调用逻辑可以共用剩下的 20% 再通过条件编译和平台 API 去单独处理。对于一个创业团队或者中小型项目来说这种成本结构非常值。从学习角度讲uni-app 也是性价比极高的切入点。它会逼着你同时接触 Vue、微信小程序语法、移动端适配甚至原生打包的底层概念一套学习投资换回来的是一个完整的全栈跨端视角。1.2 菜鸟到大佬的学习路线拆解我经常跟新人说学 uni-app 的路线其实就四步。第一步先把 Vue 的核心语法过一遍尤其是模板语法、计算属性、组件通信和生命周期这几块是 uni-app 的地基。第二步用 HBuilderX 创建项目把页面跳转、数据绑定、事件处理、请求封装这些高频操作跑通。第三步开始接触条件编译和平台差异化适配弄明白哪些 API 是多端通用的、哪些是某个平台特有的。第四步才是性能优化、原生插件封装、自定义组件库设计这些进阶内容。这套路线的关键在第三步很多人到第二步就用 “能跑就行” 的心态应付过去了一遇到真机上的兼容性问题就开始抱怨 uni-app 不行。老实说框架能不能发挥价值很大程度取决于你对不同平台运行机制的理解。你只有理解微信小程序的 setData 更新机制、理解浏览器端的 DOM 渲染差异、理解 App 端 webview 和原生层的通信成本才能真正把 uni-app 用好。1.3 HBuilderX 为什么是官方推荐HBuilderX 是 DCloud 官方出品的 IDE也是 uni-app 开发的首选工具。有人会问我可以用 VSCode 写 uni-app 吗可以但如果你是纯新手我强烈建议先从 HBuilderX 开始。为什么因为它把门槛压得足够低。HBuilderX 里内置了 uni-app 的完整编译链路。你点一下运行按钮它就能直接拉起微信开发者工具或者内置浏览器跑 H5你点一下发行它就能做云打包生成 App 安装包不需要你去手动配置安卓 SDK 和 iOS 证书这一点对没接触过原生开发的开发者来说简直是救命级的功能。再加上它自带代码提示和模板语法校验新手写错一个引用它很多时候能当场给你标出来。当然HBuilderX 也不是万能的。等你的项目逐渐变大尤其是需要更复杂的代码编辑体验、更精细的 TypeScript 支持时很多人会切到 VSCode 或者 WebStorm 配合 uni-app 的 CLI 工程来用。但那是进阶路线不是第一阶段要考虑的问题。第一步老老实实把 HBuilderX 用明白把 uni-app 的编译和运行机制建立起来后面再切换工程形态会轻松很多。2. 开发环境搭建与项目创建2.1 HBuilderX 安装与初始化配置安装 HBuilderX 这一步基本没什么难度直接去官网下载对应系统的安装包解压就能用。Windows 下我建议解压到非系统盘比如 D 盘的开发工具目录下避免权限问题。装好之后第一次启动会弹出初始化设置默认的编码格式选 UTF-8主题看你个人习惯就好。真正值得花时间设置的是“运行配置”这一块。菜单栏打开 工具 → 设置 → 运行配置你会看到各个小程序平台的开发者工具路径配置。这里需要先把微信开发者工具装好然后在这项配置里选到微信开发者工具的安装目录具体到小程序项目的导入路径那一层。这一步不做的话你在 uni-app 里点运行到微信小程序HBuilderX 会提示找不到开发者工具没法自动拉起调试窗口。还有一个容易被忽略的选项在运行配置里可以设置“自定义打印日志等级”建议开发阶段用“普通”或者“详细”方便看编译输出过程中暴露的问题。等后续真正排查问题的时候再切到对应级别去看原生层的日志。2.2 创建第一个 uni-app 项目打开 HBuilderX 之后点击工具栏的“新建项目”弹窗左侧类型选“uni-app”右侧要填几个内容项目名称、存放路径、模板类型、Vue 版本。模板类型默认有几个选项默认模板、默认模板推荐的空项目模板包含基础 tab 结构和组件引用很多官方示例都用它、空白模板连 pages.json 都是空的、以及基于 uni-ui 的模板。我的建议是第一次学习选“默认模板”因为它自带了一个 tab 切换的 demo 和几个官方组件能让你一开始就看到一个完整能跑的案例。等你想从零梳理自己项目的目录结构了再考虑空白模板。Vue 版本这里要重点说。现在新版 HBuilderX 创建项目时默认是 Vue3 版本对应的是 uni-app 3.x 的编译模式。Vue3 版本在性能、组合式 API 支持、TypeScript 友好度上都比 Vue2 版本强很多。如果你是小程序项目或者涉及老旧依赖可以考虑 Vue2但新课学习我建议直接选 Vue3跟上前沿生态也避免后面再迁移。创建完成后HBuilderX 会默认打开项目的文件和目录结构并且会自动编译一次。如果右下角没有任何红色报错说明基础链路已经通了。2.3 项目目录结构全解析很多新手一看到 uni-app 的项目结构就懵这不是 Vue 的标准目录结构啊pages.json 是什么App.vue 为什么放在根目录manifest.json 又是干嘛的这里我逐个拆开讲。先看最核心的四个文件pages.json全局页面路由和窗口表现配置。这个文件决定了你的应用有哪些页面、页面的导航栏长什么样、tabBar 是哪些页面、页面之间怎么跳转。在 uni-app 中新增页面的时候必须在这个文件里注册页面路径否则编译会直接报错。manifest.json应用级别的配置。里面记录了应用名称、AppID、小程序 AppID、各种 SDK 配置以及各平台的图标和启动图设置。你要发布到微信小程序、打包 App都得先在这里把对应的信息填好。App.vue类似 Vue 入口组件的根组件。它本身不负责渲染页面 UI而是承载整个应用的生命周期函数和全局样式。你在 App.vue 里写的globalData可以当作全局数据仓库来用但注意它不是响应式的修改后要配合事件或者 Vuex/Pinia 才能触发页面更新。main.jsVue 实例化入口。创建 Vue 应用、挂载插件、引入全局混入都在这里操作。Vue3 版本的 uni-app 里通常还会用来注册 Pinia 等状态管理工具。再看目录结构pages/存放所有页面文件每个页面通常由四个同名的文件组成.vue、.nvue可选用于 App 端的原生渲染、.scss页面样式也可以直接写在 .vue 里、.json页面局部配置可以不建。static/静态资源目录。图片、字体、视频等直接放在这里会被原样打包到各个平台。注意uni-app 对 static 目录的文件引用方式和常规 Vite 项目不太一样图片建议直接用绝对路径/static/xxx.png引用尽量避免动态拼接路径那些骚操作因为跨端后路径解析经常出问题。uni_modules/uni-app 组件和插件的统一存放目录类似 npm 的概念但又有区别。后面你用 uni-ui 或从插件市场下载的组件基本都会进到这个目录。components/普通组件目录可选的。对于不通过插件市场安装的、自己封装的自定义组件我会习惯放在这个目录下并按页面维度或功能维度建子目录方便维护。理解这些目录的含义之后你再去读官方模板代码会感觉通顺很多。项目不是靠背路径记住的而是靠理解每个文件的职责记住的。3. 核心语法与页面开发实操3.1 页面三剑客template、script、styleuni-app 的单文件页面结构跟 Vue 完全一致一个.vue文件里包含了 template结构、script逻辑、style样式三个部分。但因为要输出到小程序端有些细节跟写浏览器页面时完全不同。先看 template。uni-app 里负责页面结构的内置标签比如view、text、image、scroll-view等在编译到小程序端时会变成小程序的原生标签。你写小程序习惯用view写 uni-app 也一样。至于 HTML 标签像div、span、img在 H5 端能用但编译到小程序端会出问题所以从一开始就养成用 view 和 text 替代 div 和 span 的习惯能省掉后面大量跨端排查时间。style 部分有一个重要概念rpx。rpx 是 uni-app 定义的一种响应式单位设计稿宽度是 750rpx。比如你拿到一个 375px 的设计稿里面一个按钮宽度是 300px直接写成 600rpx 即可因为屏幕实际宽度 750rpx。这样一来同一个样式写在不同屏幕宽度的手机上元素会按比例缩放实现自适应的效果。习惯上我会用 rpx 处理尺寸和间距用百分比处理布局宽度用 px 处理 1px 边框之类需要固定物理像素的场景。script 部分核心是 Vue 的选项式 API 或者组合式 API。以 Vue3 项目为例我现在更推荐用script setup的写法代码会更简洁。下面的例子是页面里最基础的数据绑定和事件处理template view classcontainer text classtitle{{ greeting }}/text button typeprimary clickupdateGreeting点我换一句/button /view /template script setup import { ref } from vue const greeting ref(你好uni-app) const updateGreeting () { greeting.value 你学会跨端开发了 } /script style scoped .container { padding: 40rpx; } .title { font-size: 36rpx; color: #333; } /style这里greeting如果用 Vue3 的响应式 API 来维护就要记得通过ref包一层后续在模板里使用时会自动解包但在 script 里修改要用.value。很多新手就是忘记.value导致修改不生效我后面会专门讲配置自动导入来规避这类手写疏漏。3.2 页面跳转与传递参数页面跳转在 uni-app 里是通过uni.navigateTo或者uni.switchTab这类 API 实现的。navigateTo适合跳转到普通页面它会通过路由栈压栈完成跳转新页面有一个原生导航栏返回按钮最接近大家熟悉的页面栈思路。switchTab只能用来跳转 tabBar 页面也就是你配置在 pages.json 里 tabBar 列表中的页面。跳转传参是新手最容易出错的一块。参数统一放在 URL 上接收端在onLoad生命周期里通过options拿到。但是这里有一个跨端差异如果你的参数里包含对象直接用JSON.stringify转成字符串再放到 URL 上会非常通用在接收端再用JSON.parse解析回来。千万别直接把对象塞到 URL 里面某些平台可能会帮你做隐式转换结果拿到的值是[object Object]排查起来特别恶心。下面给一个我平时最常用的传参写法// 发起跳转 const detail { id: 1001, source: list } uni.navigateTo({ url: /pages/detail/detail?data${encodeURIComponent(JSON.stringify(detail))} }) // 页面内接收 onLoad((options) { if (options.data) { const detail JSON.parse(decodeURIComponent(options.data)) console.log(收到的参数, detail) } })重点解释两个细节。第一URL 上不要直接拼JSON.stringify的结果因为转出来的字符串里可能包含特殊字符要么在 URL 拼接阶段被截断要么微信等平台直接报语法错误所以要用encodeURIComponent和decodeURIComponent做转义和解码保证数据传输不出幺蛾子。第二如果只是传一个 id就没必要搞这么麻烦直接url: /pages/detail/detail?id1001就完了记住传参原则轻量参数直接传结构复杂再序列化。3.3 生命周期应用级和页面级uni-app 的生命周期分三层应用级生命周期、页面级生命周期、组件级生命周期。组件级生命周期跟 Vue 一致这里不展开重点是前两个。应用级生命周期挂在 App.vue 里主要是onLaunch应用初始化、onShow应用切入前台、onHide应用切入后台。如果你的应用需要在启动时检查登录状态、获取全局配置、初始化一些 SDK就在onLaunch里做。注意onLaunch里不能等数据加载完成后再渲染页面因为它不阻塞页面展示。页面级生命周期是 uni-app 的核心。最常用的是onLoad页面初次加载、onShow页面每次展示包括从后台切回来、onReady页面首次渲染完成、onHide页面隐藏、onUnload页面卸载。如果你做过小程序这套生命周期和微信小程序的页面生命周期几乎一一对应。在实战中少量数据的拉取我习惯放在onLoad里需要每次进入页面都刷新数据的时候我宁愿放在onShow里避免用户从详情页返回列表页时看不到最新状态。这里提一个容易写错的点Vue3 组合式 API 写法下onLoad不是 Vue 自带的而是 uni-app 提供的钩子它需要在script setup中显式导入script setup import { onLoad, onShow, onHide } from dcloudio/uni-app onLoad((options) { console.log(页面参数, options) }) onShow(() { console.log(页面展示) }) onHide(() { console.log(页面隐藏) }) /script如果你把 Web 端常用的onMounted直接拿过来当页面加载事件用在某些平台下可能不能保证它和页面的渲染时序对齐。我的习惯是涉及页面路由和展示状态的逻辑优先用 uni-app 的页面生命周期涉及组件内部真实 DOM 操作才考虑onMounted。4. 条件编译与多端适配实战4.1 条件编译的三种写法很多人在 uni-app 里做到真机调试那一步才意识到原来不同平台的差异是避不开的。uni-app 提供了条件编译机制允许你在代码中通过特殊注释让某一段代码只在小程序端编译、只在 App 端编译或者在 H5 端编译。简单理解预编译注释就像给代码贴上平台标签编译到目标平台时才保留对应标签下的代码。三种位置都能写条件编译。第一种是在 template 中!-- #ifdef MP-WEIXIN -- view这个是微信小程序专属的结构/view !-- #endif -- !-- #ifdef H5 -- view这个是 H5 专属的结构/view !-- #endif --第二种是在 script 中// #ifdef APP-PLUS console.log(只在 App 端输出) // #endif第三种是在 style 中/* #ifdef MP-WEIXIN */ .bar { height: 40px; } /* #endif */这里开头写的是#ifdef表示“如果包含”也就是指定平台存在时编译这一段。另外还有#ifndef表示“如果不包含”用于排除某个平台。注意这些注释不是普通注释中间不能乱加字符注释符号和条件关键词之间也不能有空格不然编译指令就失效了。4.2 平台差异化适配的经典案例条件编译最常见的场景就是小程序导航栏和 App 状态栏的差异处理。比如在微信小程序里页面顶部的胶囊按钮位置会占用屏幕空间你的自定义导航栏高度需要动态计算而在 H5 和 App 端没有胶囊直接用一个固定导航栏高度就行。我举个实际的适配案例。下面这段代码会根据平台选择不同的顶部留白template view classcustom-nav :style{ paddingTop: statusBarHeight px } text classnav-title自定义导航/text /view /template script setup import { ref, computed } from vue const statusBarHeight ref(20) // #ifdef MP-WEIXIN const sysInfo uni.getSystemInfoSync() statusBarHeight.value sysInfo.statusBarHeight // #endif /script在微信小程序端系统状态栏高度不是固定值刘海屏、挖孔屏跟普通屏差距非常大所以要用uni.getSystemInfoSync()拿到真实高度来动态撑开而在 H5 端浏览器里通常没有这个问题直接给一个安全值即可。通过条件编译这段差异化逻辑只写在同一个文件里干净利落。另一个高频场景是支付和分享。微信小程序支付走uni.requestPayment但具体的支付参数需要后端跟微信支付那边对接好App 端走同样的 API内部封装则是原生模块。如果你要接支付宝支付在 App 端还需要单独处理。这些商业逻辑没法靠一套代码全搞定条件编译能保证你相关分支代码存在于对应的平台产物里而不会串台。4.3 真机和模拟器的差异排查思路到了多端适配你就需要一个固定的排查顺序不然会被各种诡异问题带偏。我总结的流程是先在 H5 端跑通页面逻辑再跑微信开发者工具检查小程序端的表现最后用真机预览做 App 端验证。三步走下来大多数问题都能定位到具体是哪一端的差异引起的。如果 H5 端正常、小程序端样式错乱优先检查三件事第一是否用了不兼容的内联样式第二是否存在rpx和px混用导致的宽度计算差异第三是否用到了条件编译却没有正确覆盖到小程序分支。同理如果小程序正常、App 端表现异常优先考虑 webview 渲染性能和环境差异比如某些 API 在 App 端需要原生插件配合才能使用。5. HBuilderX 效率配置与自动导入 ref5.1 设置 uni-app 自动导入 ref 的方法热词里那个“uni-app 设置自动导入 ref”我们要认真对待。很多同学用 Vue3 组合式 API 写 uni-app 时几乎每个页面都要重复地写import { ref, computed } from vue。步骤一多人总会有手滑的时候比如忘记了导入或者只导出了其中一个编译直接报错。手动导入本身没什么问题但如果整个项目的页面都坚持手写不仅效率低代码噪音也大。这里我提供一条相对合规又实用的自动导入方案。uni-app 的 Vue3 项目底层基于 Vite你可以通过unplugin-auto-import这个插件来实现自动导入 Vue API不需要每个页面手动引入。首先在项目根目录找到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, uni-app], dts: src/auto-imports.d.ts }) ] })这段配置的含义是在编译过程中自动帮你从 Vue 和 uni-app 中引入被使用的 API。配置了imports: [vue, uni-app]之后你直接写ref、computed、onLoad这些 API插件会检测到并自动补全导入语句。dts参数会生成一个声明文件方便编辑器做类型提示这个文件建议提交到仓库保证团队协作时光标悬停提示一致。配置完成后重启一次 HBuilderX再打开一个.vue文件测试在一个script setup里不写任何 import直接使用const count ref(0)如果编辑器不再报“找不到 ref”的错误说明自动导入已经生效。实际运行到浏览器和微信小程序控制台没有提示模块导入错误就能放心使用这种写法了。需要提醒的是自动导入属于开发体验增强工具它最终生成的编译产物里依然会包含正确的 import 语句所以不会影响运行时的性能。不过如果你项目的另一个同学没有安装同一个插件或者团队统一约定必须显式写 import就要先沟通好不能各写各的不然合并代码时会很痛苦。5.2 其他值得开启的 HBuilderX 效率配置自动导入 ref 之外HBuilderX 还有几个实用配置值得调。第一个是“重命名重构”。鼠标放在变量名上按 F2可以直接全局重命名。这个功能对 JavaScript 动态语言来说非常有用尤其是你发现自己初始命名不够好的时候。它默认会关联分析作用域比手动查找替换精准得多。第二个是“内置浏览器预览”。在运行设置里选“内置浏览器”点运行到浏览器之后页面会显示在 HBuilderX 的内置浏览器窗口里并支持自动刷新。对日常调页面非常友好不用频繁切浏览器窗口。缺点是内置浏览器的渲染表现和 Chrome 不完全一致所以做最终样式检查时我还是会切到 Chrome。第三个是“代码片段”。在工具 → 代码片段设置里可以自定义快捷代码片段。比如我常年存一条 “page 页面模板” 的片段输入upage就能自动生成一个页面骨架包括 template、script、style 的注释占位。这种自定义片段用量起来很爽比每次复制粘贴完整页面省力很多。6. 常见问题与排查技巧实录6.1 页面白屏和路由跳转失败页面白屏最常见的原因集中在 pages.json 配置缺失。只要你漏了注册页面或者路径写错启动时基本黑屏或者无法跳转。遇到这种情况我建议先看控制台的编译日志uni-app 的报错信息一般会把缺失的页面路径直接列出来照着补齐就行。还有一类白屏是组件渲染报错导致的。比如你在页面上引用了某个组件但组件内部抛出了一个未捕获异常页面整个都渲染不出来。排查方法很简单把页面内容先注释掉一部分用二分法定位到具体是哪个组件触发的异常。别急着改逻辑先在浏览器端打开控制台看红色报错信息往往能直接定位到某个属性读到了 undefined。路由跳转失败的另一个隐蔽原因是navigateTo页面栈限制。小程序和 App 端对页面栈深度都有上限一般是 10 层。如果你在一个列表页不断地向详情页跳转超过 10 层之后再调用 navigateTo 就会直接失败。这不是 bug而是平台限制。我在电商类项目里遇到最多的就是这种问题处理方式有两种要么在跳转前判断当前页面栈深度要么改为uni.redirectTo替换当前页面避免无限叠加。深层页面场景里这两个 API 配合使用体验会好很多。6.2 样式不生效的几类原因样式问题在跨端开发里是重灾区整理一下最常见的三种“基础用错”、scoped漏写、单位看错。第一类基础用错就是前面提到的在模板里用了 div 而不是 view。在 H5 端div 会被渲染成合法标签样式正常但到小程序端div 不会映射到原生组件样式基本全丢。排查时直接把所有 div 全部替换成 view把所有 span 换 text问题大概率就消失了。第二类是组件样式隔离的问题。在 Vue 中scoped会把当前组件的样式加上属性限制保证不影响外部。但在 uni-app 的小程序端如果你需要覆盖一个第三方组件的内部样式只靠 scoped 可能不够通常需要额外的不带 scoped 的样式块或者通过小程序提供的样式穿透方法去处理。别一上来就用全局样式硬写宁可多设几个类名控制范围。第三类是 rpx 和 px 混用导致尺寸对不上。rpx是以 750 为基准的动态尺寸适合自适应px是固定物理像素适用于边框、阴影等不希望随屏幕宽度变化的部分。如果你在页面宽度上用 px 写那在不同屏幕上可能会出现明显的留白或挤压。记住这个原则布局尺寸用 rpx1px 细节用 px。6.3 真机调试连接不上以及 App 打包前的准备做真机调试最常遇到的情况是数据线插上后HBuilderX 识别不到设备。这里原因很多最常见的是驱动问题。安卓手机要想被电脑识别需要先开启开发者选项里的 USB 调试Windows 下还可能需要安装手机对应的 USB 驱动。驱动问题没有统一的万能解一般先试试重启 HBuilderX再到设备管理器里看有没有出现带感叹号的设备有的话就更新驱动。另一个高频坑是必须保证电脑和手机处在同一局域网。HBuilderX 的真机运行在大多数场景下依赖局域网通信手机连的 Wi-Fi 和电脑的网络如果不在同一个网段应用装了也连不上热更新服务表现为应用启动后空白。我一般建议直接用手机 USB 数据线调试数据线同时承担传输任务时局域网依赖会少一些故障面更小。如果你要打包发布安卓安装包在真正打包前必须处理三件事第一manifest.json里配置应用的 AppID以及图标和启动图否则打包出来的应用连快捷方式都难看第二检查是否申请了必要的权限比如相机、定位、麦克风这些权限在 manifest 里按需勾选不要贪多第三如果用到第三方 SDK比如支付、推送要先在对应的开放平台申请账号和密钥再到 manifest 里填入。这些配置不是你写代码时临时就能补齐的提前准备好能省很多无谓的等待。6.4 排除掉一个容易忽略的环境坑HBuilderX 版本和插件缓存最后一个我特别想说的问题很多人在排查其他问题时遇到过代码本身没有任何问题但编译一直报奇怪的错误。比如某个内置 API 不存在或者某个模板语法编译失败但代码明明跟官方文档一模一样。这时候九成是 HBuilderX 版本过旧或者插件缓存出问题了。HBuilderX 的发行节奏很快uni-app 的编译器也是紧跟着版本迭代的。你可以在菜单栏“使用帮助 → 版本更新”里查看最新版本有更新就直接升级。升级后如果发现项目编译异常试一下“清理项目缓存”或删除项目根目录下的node_modules再重新安装依赖基本可以解决大部分莫名其妙的编译问题。我还见过一个经典案例同事的项目在微信开发者工具里能跑、H5 端却一直报错最后发现是 HBuilderX 的旧版编译器缓存里残留了一份旧的构建产物清理缓存后立即恢复正常。面对这种环境类问题我自己的排查顺序是先关掉项目重新打开然后清理缓存再检查 HBuilderX 版本最后才考虑改代码。如果把顺序反过来很容易白白封装一个没必要的新文件最后还发现老代码是好的。关于工具链的学习我个人体会最深的还是那句老话工具是死的思路是活的。HBuilderX 和 uni-app 的每个功能背后都对应着一种跨端开发的取舍逻辑。你花时间把一个功能为什么这样设计想明白要比单纯记住一个配置项有价值得多。这套 “菜鸟到大佬” 的第一部分到这里算是对基础链路有了一个完整交代后面一部分我打算重点拆解 uniapp 生态里的状态管理、请求封装和组件库实战。到时候再接着聊。
