
韦奇这个项目代号是我给自己攒的一个跨端应用练手项目而uni-app是我最终选定的基础框架。一句话说清楚uni-app是一套基于Vue语法的跨端开发框架写一份代码可以编译到App端、H5端以及各类小程序平台。对刚开始做前端的人来说它最大的价值是省掉重复劳动你不用分别学多套UI规范、多套接口规则、多套发布流程就能把产品先跑在多个平台上特别适合做MVP验证、个人副业工具或者公司内部系统这种“需要尽快被用起来”的场景。这篇我把最基础的东西完整讲一遍包括环境准备、目录结构、核心配置、页面开发、路由跳转、跨端兼容和一些高频问题排查全程按照我实际跑过的路径来写能抄作业的地方尽量给你抄。1. uni-app到底解决什么问题1.1 跨端开发的痛苦由来说句实话做跨端最烦的不是代码怎么写而是“同一套逻辑要在不同端上各自维护一份”。以前我做过一个内部工具HTML版、App版、小程序版各来一套改一个功能要改三遍改完还得挨个验证效率低到离谱。uni-app的做法是反过来的源码只有一份编译阶段根据目标平台生成对应代码。开发时你写的是vue文件编译后小程序端会变成小程序结构App端会生成原生渲染所需的内容。你不需要关心最终产物怎么组织只要关心业务逻辑和交互状态。这套思路的本质是“编译期转换”不是运行时套壳所以性能上有保证不是拿一个WebView套个壳就去混事。这里要说清楚一个边界uni-app不是万灵药。它适合业务型应用比如商城、社区、工具、管理后台不适合重度游戏、极低层硬件交互、或者需要大量深入系统能力的应用。把它的适用边界先想清楚才不会在项目后期骑虎难下。1.2 uni-app的优势与适用边界用一句话总结优势一套Vue代码多端可用开发体验和调试工具都比较成熟。具体来说有三个点让我觉得选它值基于Vue语法上手成本低。会写Vue组件基本就掌握了八成的uni-app开发。条件编译机制很灵活同一份代码里可以针对不同端写差异化逻辑不需要拆成多个仓库。生态里插件、模板、组件库都很多遇到问题基本能搜到答案。适用边界要再强调一下如果目标只是做单个端而且长期不用扩展用原生或纯Web也完全没问题不必为了跨端而跨端。如果涉及蓝牙、NFC、センサー这类系统能力App端可用但需要插件配合评估成本时要留余地。如果团队没人懂Vue那要先补齐基础否则框架再省力也没用。我自己实际用的场景是一个管理端H5、两个C端页面、一个内部工具App三处逻辑有大量重合uni-app确实帮我把合并成本压到了最低。2. 环境准备从零跑通第一个uni-app项目2.1 工具链选择用哪种方式开发市面上跑uni-app项目有三种常见方式使用官方可视化开发工具、使用命令行工具创建工程、直接在浏览器里用在线编辑器。我推荐大多数新手走官方可视化工具原因是调试、运行、真机预览的链路都内置好了少踩很多配置的坑。命令行方式适合已经熟悉前端工程化的同学创建完工程后自己接打包脚本、配CI灵活度高但需要自己处理很多环境细节。在线编辑器适合临时验证一段代码不适合正经项目。无论选哪种方式前提是你得有点Vue基础至少知道.vue文件长什么样、data和methods怎么用。如果你连组件、模板、循环渲染都还没练过建议先花两天过一遍基础语法再回来。2.2 创建项目与目录结构用可视化工具创建新项目时模板建议选默认模板先别碰一堆奇奇怪怪的UI模板。默认模板干净能让你看清框架本身的骨架。创建完工程后浏览目录至少会看到下面这些东西├── pages │ ├── index │ │ └── index.vue ├── static │ └── logo.png ├── App.vue ├── main.js ├── manifest.json ├── pages.json └── uni.scsspages目录放页面每个子目录通常放一个页面一个页面就是一个.vue文件这是你写业务的主要战场。static目录放静态资源比如图片、字体、本地文件注意这个目录下的文件会被原样打包最好别放体积特别大的素材。App.vue是根组件全局生命周期和全局样式都写在这里。main.js是入口文件创建应用实例的代码就在这。manifest.json和pages.json这两个配置文件后续要重点讲是框架的“总开关”。2.3 运行到各端的差异初体验第一次跑项目我建议先把H5端跑通再看App端和小程序端。顺序很重要H5端依赖最少启动最快能让你先聚焦在代码本身的正确性上。跑通H5后再选择模拟器运行到App端。此时需要安装对应平台的开发环境Android端要准备对应开发工具和SDKiOS端则只能在特定平台下编译。这个环节新手容易卡住问题多半出在SDK版本不匹配解决办法是严格按照官方文档步骤走别自作主张装最新版。第一次看到同一套代码在不同端跑出不同长相不要慌这是正常的。不同端的默认UI差异、字体渲染差异、安全区域差异都会影响视觉后续通过条件编译和样式适配来收敛。3. 核心概念配置文件与入口逻辑3.1 pages.json页面路由与导航栏的配置中心pages.json是uni-app里最不能忽视的配置文件它至少管了三件事页面注册、页面路由、导航栏外观。先说页面注册。新建一个页面后必须在这里的pages数组里登记路径否则运行时会找不到文件。这个步骤很多人会漏尤其是手工建页面的时候。配置例子{ pages: [ { path: pages/index/index, style: { navigationBarTitleText: 首页 } }, { path: pages/detail/detail, style: { navigationBarTitleText: 详情 } } ], globalStyle: { navigationBarTextStyle: black, navigationBarBackgroundColor: #FFFFFF, backgroundColor: #F2F2F2 } }pages数组的第一个元素是应用启动后默认展示的首页这个顺序有讲究别随手调整。style里的navigationBarTitleText是页面顶部导航栏的标题文字小程序端和App端都会读取。globalStyle里的配置是全局兜底——某个页面没有单独配导航栏样式时就用这里的值。如果你要做底部标签栏需要在tabBar字段里配置。一个最小可用配置是这样tabBar: { color: #999999, selectedColor: #FF5A00, list: [ { pagePath: pages/index/index, text: 首页 }, { pagePath: pages/mine/mine, text: 我的 } ] }这里有个容易踩的坑tabBar里的pagePath必须和pages数组里的path完全一致不能多斜杠也不能少后缀。另外有的端要求tabBar项必须带iconPath不带icon可能直接报错所以实际项目里还是老老实实准备图标深色和浅色各一套。3.2 manifest.json应用级配置manifest.json是应用的身份信息中心应用名称、版本号、平台配置全在这里。可以把它理解为应用在不同平台上的“素材登记表”。最常见的操作是修改应用名称。配置App的图标、启动图。配置小程序端AppID。配置App端的权限模块。这里我想强调一件事很多刚上手的人改完manifest.json后发现没生效原因是不同的配置项在不同的编译阶段才会被读取。比如小程序AppID只有在小程序端运行或打包时才用得到App图标则会在App打包时读取改完配置记得重新编译别只刷新页面。3.3 main.js与App.vue的作用main.js是框架启动入口。Vue 3版本的uni-app默认入口长这样import App from ./App import { createSSRApp } from vue export function createApp() { const app createSSRApp(App) return { app } }这个文件日常很少改但要知道它存在。App.vue则是全局根组件可以在这里定义全局生命周期和全局样式script export default { globalData: { userInfo: null }, onLaunch() { console.log(应用启动) } } /script style /* 全局样式所有页面都会继承 */ page { background-color: #F5F5F5; } /styleglobalData是一种很轻量的全局数据容器适合存放登录状态、用户信息这类基础数据。页面里用getApp().globalData.xxx就能拿到。注意它是内存级别的App被杀死后数据就不在了不能代替持久化存储。4. 页面开发基础模板、样式与组件的正确用法4.1 模板写法与vue文件结构一个uni-app页面就是标准的vue单文件组件包含template、script、style三块。模板部分用view、text这类内置组件搭建结构比如template view classcontainer text classtitle{{ title }}/text view classcard v-for(item, index) in list :keyindex {{ item.name }} /view /view /template script export default { data() { return { title: 韦奇基础示例, list: [ { name: 第一项 }, { name: 第二项 } ] } } } /script style .container { padding: 30rpx; } .title { font-size: 36rpx; font-weight: bold; } .card { margin-top: 20rpx; padding: 24rpx; background: #ffffff; border-radius: 16rpx; } /style写模板时要记住uni-app的模板里不区分HTML标签div、span这些在App和小程序端是不识别的必须使用view、text、image等内置组件。有人刚转过来时老是不自觉写div跑起来发现布局全乱了这个习惯要刻意改。4.2 样式单位的坑rpx到底是啥rpx是uni-app引入的响应式单位也是新手最需要理解的一个概念。它定义得很简单屏幕宽度固定为750rpx。也就是说在750宽的设计稿里1rpx就等于1px在375宽的屏幕上1rpx就是半个px。这样一来你写一个width: 750rpx不管跑在宽屏还是窄屏元素都会占满整行免去了手写媒体查询的痛苦。实践里的换算方法是设计稿上量到多少px就填多少rpx前提是设计稿宽度是750。有几个细节要提醒rpx不是所有属性都适用比如字体大小、边框粗细这类也要用rpx但个别端可能出现四舍五入导致像素偏移遇到就再微调一下。在H5端rpx的表现偶尔会和你预期不完全一致特别是嵌套flex布局时。遇到这种情况可以先检查父容器有没有正确设置宽度。文本内容里涉及多行截断的样式不同端差异比较大建议用固定行数加上overflow处理别依赖纯高度计算。4.3 内置组件与自定义组件内置组件是uni-app帮我们封装好的基础元素主要记住几个高频的view布局容器相当于div的角色。text文本展示支持部分富文本能力。image图片展示注意mode属性决定裁剪和显示方式。scroll-view可滚动区域适合做横向滑动、列表滚动。swiper轮播容器做 banner 很方便。navigator页面跳转的声明式组件可以理解为带跳转能力的文字或区块。自定义组件和Vue里的组件写法完全一致创建在components目录下页面里import并注册就能用。需要注意命名组件标签最好用双单词形式避免和内置组件或未来新增组件重名比如product-card这种。5. 路由跳转与参数传递5.1 声明式导航与编程式导航路由跳转有两种姿势。声明式导航就是用navigator组件包一层navigator url/pages/detail/detail?id100 hover-classnav-hover 跳转详情 /navigator编程式导航用API来跳uni.navigateTo({ url: /pages/detail/detail?id1001, success: (res) { console.log(跳转成功, res) }, fail: (err) { console.error(跳转失败, err) } })我建议能写API就别用组件因为业务里经常需要在跳转前判断登录状态、做数据统计用编程式导航更方便统一处理。url路径必须以/开头相对路径很容易出问题。5.2 参数传递的三种方式跳转带参是日常需求常见三种方式第一种是URL传参上面例子已经演示了。接收页面在onLoad里取onLoad(options) { console.log(options.id) }注意URL传参只能传字符串对象要先用JSON.stringify转换接收时再解析。有特殊字符时要先encodeURIComponent否则参数会被截断。第二种是通过全局变量globalData传参适合不太敏感的基础数据比如用户身份、登录状态。第三种是本地存储适合需要跨页面持久化的数据。写入用uni.setStorageSync(key, value)读取用uni.getStorageSync(key)。这个方法在小程序端和App端都好使但要注意别把太大对象一次性塞进去读写频繁的场景性能会受影响。5.3 页面通信不用路由也能传数据有时候两个页面没有直接的跳转关系但需要同步最新数据。这种情况可以靠事件总线来做解耦。简单做法是在main.js里导出一个Vue实例或者自己实现一个极简订阅发布器// utils/eventBus.js const handlers {} export function on(event, handler) { if (!handlers[event]) { handlers[event] [] } handlers[event].push(handler) } export function emit(event, data) { if (handlers[event]) { handlers[event].forEach((handler) handler(data)) } } export function off(event, handler) { const index (handlers[event] || []).indexOf(handler) if (index -1) { handlers[event].splice(index, 1) } }页面A里监听页面B里触发// 页面A onLoad 里 import { on } from /utils/eventBus on(userUpdated, (userInfo) { console.log(userInfo) }) // 页面B 里 import { emit } from /utils/eventBus emit(userUpdated, { name: 韦奇 })用这种模式要注意及时取消监听否则页面销毁后回调还会执行容易内存泄漏和重复触发。一般放在onUnload或onHide里处理。6. 跨端兼容实战技巧6.1 条件编译的语法与场景不同端的差异不可避免比如H5环境没有小程序的分享功能App端需要调用更底层的能力。uni-app给出了条件编译机制在注释中写编译范围目标端才会保留这块代码。平台判断语法如下// #ifdef H5 console.log(只在H5端编译) // #endif // #ifndef H5 console.log(不在H5端时编译) // #endif样式里也可以用/* #ifdef H5 */ .container { padding-bottom: 50px; } /* #endif */模板里也可以!-- #ifdef H5 -- view只在H5端显示/view !-- #endif --我的经验是条件编译尽量收敛在工具函数和组件内部别撒得满页面都是否则代码读起来像打补丁维护成本飙升。遇到跨端差异先试着从通用的Vue语法层面解决解决不了再用条件编译兜底。6.2 API能力差异与polyfill思路uni-app把大部分跨端API统一成了uni.xxx的调用方式比如uni.request、uni.showToast、uni.navigateTo。但是不同端对同一个API的支持深度不一样比如H5端没有某些原生能力小程序端的缓存容量限制也比App端严。应对策略我常用的有三个第一写一个薄的API封装层把常用请求、存储、跳转统一封装方便替换实现。第二用条件编译提供端级降级方案。比如App端可以读取更完整的设备参数H5端就拿不到那就返回空对象或提示不支持。第三调用不支持的API前先用能力判断不要直接假设所有端都有某个方法。比如uni.getSystemInfoSync()有些字段在不同端返回格式不同代码里做好容错处理。我在实际项目中踩得最多的不是API缺失而是端与端之间返回字段命名不一致。处理办法是加一层数据适配函数在请求完接口后统一转换格式别让业务代码散落着各种端判断。7. 常见问题与排查心得7.1 样式不一致问题同一套样式在不同端渲染出不同结果这是跨端开发最普遍的痛。常见原因有几个默认字体和默认行高不同。解决方式是在全局样式里把字体、行高、颜色统一。部分CSS属性某些端不支持。比如小程序的按钮默认样式和H5差异大自己写按钮样式时建议把所有默认样式重置一遍。边框、圆角处理的细微差异。多加一条box-sizing: border-box能少很多麻烦。排查思路是先在H5端把基础样式做好再去其他端对照每差异一处就记录一处最后汇总成自己的兼容清单。我这份清单现在越写越长踩一个记一个回头效率提升特别明显。7.2 生命周期差异与嵌套坑页面生命周期里最容易忽略的是onLoad和onShow的区别。onLoad只在页面第一次加载时触发一次onShow每次页面显示都会触发。返回上一个页面时目标页面的onShow会被再次调用但onLoad不会。如果你有“每次进入页面都刷新数据”的需求就写在onShow里别写在onLoad里。反之只想在首次加载时执行一次的初始化逻辑放到onLoad里更合适。组件生命周期的坑在于组件挂在页面里后它的Vue生命周期会按正常流程走但页面级的onLoad和组件级的created哪个先执行很多人分不清。实际上组件创建在页面onLoad之前是可能的所以不要在页面onLoad里依赖组件内部的初始化结果把这种逻辑放到组件自己的mounted里去做。7.3 真机调试与报错排查真机调试是绕不开的一环模拟器上没问题一上真机各种状况就冒出来了。常见几个点启动时报错优先看manifest.json的平台配置是否完整比如App图标是否有对应尺寸。接口请求失败先看是否开启了本地调试代理以及真机与开发机是否在同一局域网。某些端在开发者工具里能跑真机却卡在启动页多半是资源路径写错或依赖的原生插件没配置。排查报错有个笨但有效的习惯先把出错页面缩减到最小可复现状态一行一行注释代码直到找到触发问题的语句。别一上来就怀疑框架大概率还是自己代码的问题。我还遇到过一个迷惑现象同一段逻辑在H5端正常到App端表现就不符合预期。后来发现是Promise异步时序问题H5和App的JS执行环境有细微差异。这种问题最有效的工具就是在关键节点加日志把执行顺序打出来对比。7.4 几个高频问题速查表问题现象可能性原因处理方式页面导航栏标题没变化pages.json里改错页面路径核对pages数组里的path是否和pages目录一致跳转按钮没反应url没以/开头或目标页未注册补上/并在pages.json注册页面tabBar页面跳转失败用了navigateTo跳tabBar页改用uni.switchTab页面底部被系统导航栏遮挡未适配安全区域使用env(safe-area-inset-bottom)处理内边距图片在App端显示不出来路径用了相对路径或带空格使用绝对路径或static下的资源路径8. 韦奇项目里我保留的几个习惯最后分享几个我在跑韦奇这个项目时保留的习惯算不上什么秘诀但是很管用。一是每个页面尽量只负责自己的一块职责。不要用一个巨型页面把所有业务逻辑堆在里面拆成组件后不只是代码好看调试时也方便定位问题。页面级只留数据汇总和交互入口展示细节交给子组件。二是公共请求、工具函数一定要集中管理。我在项目里会建一个utils目录专门放request封装、本地存储封装、格式化工具。业务代码永远不直接调用uni.request为什么不直接调用因为一旦登录逻辑、token刷新、错误弹窗需要统一处理只要改封装层一处就够了。这个习惯帮我省下了大量重复劳动。三是写代码之前先想清楚跨端差异点。每接一个需求我会把涉及平台差异的地方列出来一次性处理而不是等都跑一遍才暴露问题。比如涉及到系统剪贴板、地图、相机这类能力早做兼容方案比事后补救轻松得多。四是经常看官方文档的更新记录。uni-app的版本升级频率不低有些端的新能力支持情况也会变化。我习惯每次升级后跑一遍现有页面的核心流程确认没有回归再继续开发。这个习惯看起来慢但能避免在发布前才发现致命问题。韦奇这个项目现在还在继续迭代基础的部分讲到这里基本够你把项目跑起来、页面写出来、跳转和传参理顺了。后面再遇到更具体的问题建议优先看编译日志很多错误信息已经把答案写在里面了静下心读一读比到处搜资料高效得多。