尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

uniapp组件体系全解:跨端适配、通信与项目实战

uniapp组件体系全解:跨端适配、通信与项目实战 每次做完一个uniapp项目都会有朋友跑来问我为什么同样的页面在微信小程序上好好的一到App端就白屏为什么封装好的组件H5能跑App上却不显示这些问题大多数都出在同一个地方——对uniapp组件体系的理解不够透彻。说到uniapp的组件很多刚从vue转过来的人第一反应是“不就是vue组件嘛”对但不完全对。uniapp的组件横跨了小程序、App、H5三大端同一套代码要跑在三套不同的渲染引擎上里面的门道远比想象中复杂。我从最早用vue2的uniapp做小程序到现在用vue3技术栈跑App和鸿蒙踩过的坑攒了满满一筐这篇文章就是把这几年和组件打交道的经验梳理一遍从内置组件到自定义组件从组件通信到平台适配把关键部分拆开讲透希望能帮正在做uniapp开发的你少走几条弯路。这篇文章适合刚入门uniapp的新手也适合已经在写业务但经常被跨端问题折磨的开发者。1. uniapp组件体系全景拆解1.1 内置组件跨端一致的能力底座uniapp官方内置了view、text、image、scroll-view、swiper、video、map、canvas、web-view等几十个基础组件。这些组件在小程序端会被映射成小程序原生组件在App端会被映射成对应的原生视图或由内置webview渲染在H5端则生成标准的HTML元素。同一个view组件小程序里编译成viewH5里可能就变成了div。这里就引申出一个关键点内置组件的跨端一致性是“能力层面”的一致而不是“样式层面”的绝对一致。比如view在小程序端有hover-class属性H5端没有button组件在不同平台上的默认样式差异很大直接拿一套CSS想同时喂饱三个端几乎不可能。实操中我的习惯是所有内置组件尽量只使用uniapp官方文档明确列出的公共属性凡是标注了“仅App平台支持”或“仅微信小程序支持”的属性必须用条件编译单独处理。另外要注意内置组件的原生能力是有性能差异的比如scroll-view在长列表场景下小程序端用enhanced属性开启增强模式后滚动会更顺滑而App端如果渲染几千条节点最好启用recycle或改用virtual-list方案否则滚动掉帧会很明显。还有一个容易被忽略的点是内置组件的层级问题。在小程序端video、map、canvas这类原生组件天然盖在普通view上面传统CSS的z-index对它们无效。虽然现在微信小程序推出了same-layer渲染机制但并非所有组件所有场景都支持。所以设计弹窗、下拉面板、自定义导航栏时必须提前避开原生组件层或者在切换弹窗时动态隐藏原生组件否则就会出现“弹窗被视频盖住”的应用事故。1.2 自定义组件业务复用与平台差异屏蔽自定义组件是uniapp业务开发的绝对主力。把重复出现的页面区块封装成组件把复杂交互拆进组件内部这既是vue的基本功也是uniapp项目的保命策略。因为只有把差异屏蔽在组件边界之内页面层才能保持干净跨端问题才不会到处扩散。我封装组件时会遵循几条原则。第一组件只做展示和交互回调不直接发请求改全局状态所有数据由父组件通过props注入交互结果通过emit抛出保持组件的纯函数属性。第二凡是涉及平台差异的代码全部在组件内部用条件编译收口页面层根本不需要感知某个功能在App上要调原生插件、在小程序上要调云函数。第三组件内部不写死业务文案和样式尺寸通过props和custom-class或customStyle留出扩展口子否则换一个UI风格就要重构组件。这里特别想提一下easycom规范。uniapp支持easycom自动引入组件只要组件放在components/组件名/组件名.vue目录下页面中直接写组件名 /不用手动import也不需要注册。这个机制我自己项目中一直在用配合uni_modules生态装第三方组件库时几乎零配置。不过需要注意两点第一easycom的组件名不能和内置组件重名第二自定义easycom规则时easycom.json里的autoscan字段默认是true但如果你同时配置了自定义规则可能会覆盖默认扫描目录新加的组件忽然就找不到了排查起来容易懵。2. 组件通信父传子、子传父、跨层级的完整方案2.1 最基础的props与emit组件通信是uniapp面试题里的常客也是日常业务里最容易写出“屎山”的地方。最基本的父传子就是props子传父就是$emit事件这跟vue完全一致没什么好说的。props传参时有一个高频坑小程序端不支持在props里直接传函数也不支持在模板里调用组件内部的方法。很多人写vue写习惯了父组件给子组件传一个回调函数H5上跑得好好的小程序端却收不到更新或者事件不触发。正确的做法是统一用emit抛事件由父组件监听事件去执行逻辑保持数据流单向清晰。还有一个细节是props默认值和类型校验。type不写、默认值不写在H5端没事在小程序端一旦父组件传入undefined组件内部可能直接报错。我的建议是每个props都声明type和default数组和对象类型的默认值必须用工厂函数返回。另外props命名不要用驼峰带首字母大写比如userName没问题但UserName在某些低版本小程序端会被转成username取不到值。emit事件同理事件名尽量全小写加短横线比如update:value写的事件名发射时用this.$emit(update:value)不要用this.$emit(updateValue)否则H5正常小程序端监听不到。这类问题不会报错只会让你莫名其妙地发现“点击之后没反应”很难查。2.2 provide/inject、全局状态与事件总线当组件嵌套层级较深时一层层props透传非常痛苦这时候可以组合使用provide/inject。uniapp的vue3版本完全支持provide/inject适合做“祖先组件向任意后代注入配置”这类场景比如主题色、全局配置对象、接口基地址等。要特别注意的是provide/inject是非响应式的默认行为。如果注入的值是普通对象改动了组件内不会自动同步。我的做法是注入一个reactive对象或者在provide时返回一个computed的引用这样后代组件读取到的值就是实时的。如果项目用了pinia通常我会把全局状态都放进storeprovide/inject只用来传store实例或特定配置避免组件里到处裸奔全局变量。说到全局状态vuex和pinia在uniapp里都能用。vue3项目我推荐pinia轻量、天然支持setup语法、ts友好。但有一点必须提醒小程序端的store持久化不能用localStorage直接读写需要走uni.setStorageSync和uni.getStorageSync。并且当你的项目拆了分包之后不在主包里的页面如果引用了主包里的store模块会出现“找不到模块”的编译错误。所以跨分包的公共代码尽量放到common或uni_modules这类不参与分包的目录。事件总线在小程序端还有一个特殊之处uni.$emit和uni.$on是官方提供的全局事件方案适合A页面通知B页面刷新这种场景但记得在页面onUnload里uni.$off掉否则会累积监听器影响性能和内存。我见过一个项目多个页面都用uni.$on监听同一个事件页面关闭后不注销结果每次触发事件所有已关闭页面的回调全跑一遍数据被反复覆盖排查起来特别费劲。2.3 动态组件与keep-alive的正确用法动态组件component :is...在uniapp中是可以用的但比纯vue项目多了不少限制。首先is属性接收的是组件的名字或选项对象。在vue3的uniapp项目中配合defineAsyncComponent可以实现异步加载组件。但这里要清楚小程序端不支持运行时动态渲染任意组件凡是import的组件必须静态出现在代码中否则编译器无法将它们打进bundle。keep-alive在uniapp里更特殊。小程序端的页面栈是原生管理的keep-alive只对组件级别的缓存有效对页面级别无效——页面销毁后状态就没了靠onLoad参数恢复。App端也一样别指望用keep-alive缓存页面。如果你需要“页面切走再回来状态不清空”的效果我的办法是把页面数据放到pinia或vuex里用onShow钩子做数据恢复切走时不清空store。另一个办法是把需要保活的区域抽成组件在父组件里用keep-alive包裹这样组件级别的缓存是有效的但要注意缓存的组件不能依赖页面的onLoad参数。组件的显隐控制也要小心。v-if和v-show在小程序端渲染逻辑不同v-if是真条件渲染频繁切换开销大v-show虽然看起来只是切换display但小程序端节点仍然保留在渲染树里。如果列表项非常多v-show反而会让初始渲染变慢。长列表里的显隐切换配合wx:if条件渲染加v-if可能是更稳的选择。总之动态组件和缓存机制在uniapp里都有平台特性没有万金油方案先搞清楚目标平台的底层行为再决策。3. 高频场景组件实操video、swiper、map、web-view、canvas3.1 video组件模块配置、全屏与视频方向video组件在uniapp项目里几乎都要用到带货类、教育类、内容类产品尤其如此。但video组件的坑也是出了名的多。先说模块配置。如果你用vue2或老版本vue3项目在App端使用video必须先在manifest里勾选VideoPlayer模块。很多新手在H5端开发一切正常打包成App后黑屏控制台还提示“未添加videoplayer模块”。这里说的是uniapp离线打包或云打包的模块配置不是页面里写代码能解决的。2023年之后的vue3版本默认集成VideoPlayer但如果你的项目是从老版本升级过来的manifest里缺失模块配置是常有的事。检查路径manifest.json - App模块配置 - VideoPlayer视频播放勾选后重新打包。第二个常见问题是视频方向。iPad或手机横屏拍摄的视频在小程序端用video组件播放正常但录制完ips视频后播放时画面转了个90度。这是因为视频文件里的rotation元数据没有被播放器正确处理。解决思路有两个一个是录制时对视频做预处理用ffmpeg这类工具把旋转角度烧录进画面另一个是播放时通过objectFitcontain配合direction属性设置播放方向。如果你是用camera组件或原生插件录制视频建议录制完成后立即读取视频的方向信息再做统一校正。video组件的层级问题同样值得单独说。小程序端video是原生组件弹窗、toast、下拉刷新这些UI都可能被video盖住常规对策是弹窗出现时调用this.$refs.video.pause()并设置v-if隐藏video或者用cover-view做覆盖层。App端uni-app的video在部分机型上也有层级问题尤其是双webview渲染模式下弹窗层和视频层的混排需要格外测试。3.2 swiper轮播图组件自动播放与复杂卡片轮播图组件swiper用来做banner图、商品图、卡片滑动是封装频率最高的组件之一。基本的autoplay、interval、circular三个属性要记牢但更值得做的是把它封装成一个通用XbSwiper组件统一处理占位图、图片懒加载、点击事件、指示器样式。我封装轮播图时一般会处理几个细节。第一图片列表为空时显示占位图避免swiper空白。第二current属性控制当前索引如果需要外部干预跳转必须用.sync或v-model方式修改否则组件内滑动和外部跳转指令会互相打架。第三swiper里每张图的高度使用height: 100%配合modeaspectFill避免不同尺寸图片撑破容器。第四如果banner图数量非常多不要把所有图片一次性塞进去用v-for渲染“当前页前后各一页”的懒渲染方案性能会好很多。还有一个很多人不知道的点swiper是可以嵌套的但纵向swiper套横向swiper时小程序端偶尔会出现手势冲突。我建议同一页面里避免横向纵向双向滑动嵌套非用不可时内层swiper设置disable-touch或外层用scroll-view代替。3.3 map地图组件定位与常用API封装map组件在有LBS需求的场景门店查询、配送定位、行程轨迹里非常有存在感。但map组件也是跨端表现差异巨大的组件之一微信小程序里一套APIApp端高德或百度一套APIAPI风格和事件回调字段都不一样。我见过不少项目直接用map组件的原生事件做业务比如markertap在小程序端能用App端部分版本里事件名变成markertap之外的小写形式甚至不触发。稳妥做法是把map组件做二次封装内部通过条件编译分别处理对外暴露统一接口。比如封装一个getCenter方法小程序端返回{latitude, longitude}App端返回高德的经纬度结构再在封装层归一化成同一格式。做法虽然有点笨但能救火。经纬度转换也是高频需求。uniapp的uni.getLocation默认返回wgs84坐标系在国内落地必须转gcj02否则地图上点位会偏移几百米甚至更远。小程序端可以通过type: gcj02直接获取H5端如果用的是浏览器定位默认就是gcj02但App端不同定位模块返回的坐标系可能不同。封装时统一在内部做转换用网上那些坐标偏移算法就行测试时注意边界。3.4 web-view组件H5与原生双向通信web-view在uniapp里承载了很多“原生能力不够H5凑”的场景比如复杂的富文本页面、大促活动页、第三方支付协议页。但web-view组件最大的问题是它是一个完全独立的内嵌浏览器与uniapp应用的数据、登录态、原生API是隔离的。在H5端uniapp项目本身就跑在浏览器里再用web-view嵌入另一个网页其实就是一个iframe。但到了App端和小程序端web-view里的网页与外部通信需要靠postMessage加特定的事件桥接。小程序端微信公众号的JSSDK、App端plus.webview的evalJS这些细节如果不在封装层处理页面间通信会很痛苦。我常用的一种桥接方案是在uniapp项目中内置一个bridge.js脚本挂在web-view加载的H5页面中。H5页面通过uni.postMessage把事件发给外层外层通过onMessage接收反过来外层通过获取web-view实例的evalJS方法调用H5页面的全局函数。注意微信小程序里web-view的postMessage不是实时到达的需要等页面分享、后退或组件销毁时才统一返回信息这是一个很多人踩了才发现的坑。如果要做实时双向通信微信小程序端建议使用wx.miniProgram.postMessage搭配wx.miniProgram.navigateBack的固定套路或者干脆换成原生组件实现这部分功能。3.5 canvas组件跨端绘制与导出白图canvas绘制是生成海报、二维码、图表这类需求的必经之路。但canvas在uniapp里的坑深得让人绝望最著名的就是iOS Safari下用canvas绘制完导出图片会得到一张白图。原因基本锁定在canvas绘制时机的差异上。iOS Safari对canvas的离屏渲染策略比较特殊如果在canvas真正完成布局前就调用了uni.canvasToTempFilePath导出内容可能是空的。解决方法是绘制完成后的回调里加一个短暂延迟或者等待canvas的draw回调真正结束再导出。还有一个技巧是在绘制前强制触发一次canvas的尺寸重置再重新走一遍绘制流程很多真机问题都能靠这个“歪招”缓解。另外uniapp的canvas分为type2d新接口和旧版接口新接口返回的是Canvas实例旧接口通过uni.createCanvasContext获取上下文。两者API完全不同。我的建议是统一用type2d的新接口因为小程序端从基础库2.9.0开始推荐新接口App端2d canvas也陆续补齐了能力新项目没必要再碰旧接口。但注意新接口在小程序端的canvas宽度默认是100%乘高度用uni.getSystemInfo拿到的窗口宽度来换算导出时destWidth要设置为canvas宽度的像素值否则导出图片模糊。3.6 常用生态组件mp-html、echarts、打印组件第三方组件是uniapp组件体系的重要补充。mp-html是富文本渲染最好的选择它比内置rich-text强大得多支持表格、视频、代码块、自定义标签在App和小程序端表现都很稳定。接入方式很简单npm安装后easycom自动注册或者手动import用法就是把rich-text替换成mp-html :contenthtmlString /。我一般还会在mp-html上做两件事一是自定义tagStyle覆盖全局样式避免H5端字体大小和App端不一致二是设置lazy-load懒加载图片长富文本页面性能会明显提升。echarts是图表组件的首选。市面上有qiun-data-charts和ucharts这类简化方案但我个人更推荐直接使用渲染到canvas的echarts。一个关键注意点是echarts在H5端直接引入npm包没问题但在小程序端不能直接用因为小程序没有DOM环境。常用方案是下载echarts的微信小程序定制版通过组件方式包一层。实际项目中我习惯把它封装成XbChart组件props里传入option对象组件内部监听option变化并调用setOption从而让业务方只需维护echarts配置不用碰渲染逻辑。打印需求在uniapp里比较特殊常见于搭配蓝牙打印机的“小票打印”场景。微信小程序可以用wx.print相关API但更多是配合第三方打印组件比如菜鸟打印组件的cnprintclient。这类组件通常走的是原生插件或web-view桥接在H5端几乎没法直接使用。我的建议是先把打印业务抽象成统一的print(template, data)方法底层对接打印机厂家SDK页面层永远只传数据和模板编号。这样即使更换打印机合作方页面代码也不用大改。4. 跨端适配、条件编译与打包发布实战4.1 条件编译让同一套代码跑遍端上条件编译是uniapp最核心的“跨端魔法”。它的原理和C语言的#ifdef一样编译时直接剔除不属于当前平台的代码块。写法有三种注释式、普通注释式和样式式。// #ifdef H5 console.log(只在H5端执行); // #endif // #ifndef MP-WEIXIN console.log(除非是微信小程序端否则执行); // #endif样式里的条件编译也很常用比如不同端设置不同的padding和字体/* #ifdef H5 */ .banner { height: 200px; } /* #endif */ /* #ifdef MP-WEIXIN */ .banner { height: 180px; } /* #endif */条件编译最容易出问题的地方是“误伤”。比如在组件里写// #ifdef H5时如果注释符号后面多了空格或者#endif漏写编译会报奇怪的错。还有条件编译只能用在.vue、.js、.css这些文件里纯.js文件里也可以写但要注意条件编译注释不能嵌套。我自己通常会在项目里维护一个platform.js集中做平台能力判断和polyfill页面代码尽量少出现条件编译维护起来更清晰。4.2 manifest配置模块、权限与应用信息manifest.json是uniapp项目的“根据地”模块勾选、appid、权限声明、SDK配置都在这里。我见过很多“功能在H5正常打包后崩溃”的案例追到最后都是manifest的模块配置漏了。拿video模块来说前面提过必须勾选VideoPlayer。map模块对应Geolocation和Map。如果用到蓝牙要勾选Bluetooth。用到本地存储、文件操作涉及FileSystem。这些模块勾选之前最好先去uni的插件市场或官方文档看最新要求因为不同版本的HBuilderX对模块名的命名有变动。还有一个容易忽略的是权限声明。Android平台打包后涉及摄像头、录音、定位、相册的权限必须在manifest里提前声明否则应用市场审核或真机运行时会被系统拦截。注意App端的权限提示文案越具体越好系统弹窗的文案直接用manifest里的iosPrivacyInfo或androidPermissions描述含糊的文案会被审核打回。4.3 原生插件与原生组件桥接uniapp支持通过原生插件扩展能力。uni-app生态里有大量原生插件从扫码、蓝牙、NFC到各种硬件控制都有。接入原生插件的流程通常是在插件市场购买或下载项目里手动添加插件到nativeplugins目录然后在manifest里配置插件id和参数最后在代码中通过uni.requireNativePlugin获取插件实例调用。原生插件是uniapp组件体系里“最强的外援”但也最容易出问题比如版本兼容、权限冲突、IOS证书配置等。我建议在使用原生插件前认真读插件市场里的评论区看看近期有没有人反馈崩溃问题最好选更新频繁、文档详细的插件。如果插件文档非常简陋、demo还跑不通果断换方案。另外原生插件在“真机运行”时通常有效但“模拟器运行”可能直接不支持调试时优先用真机否则会浪费大量时间。4.4 打包上架安卓应用市场的流程打包上架是一套独立流程这里提几个组件相关的高频坑。第一HBuilderX云打包时如果项目引用了原生插件必须勾选对应的云打包渠道并填写插件参数否则打包会失败。第二用wgt包做资源热更新时原生插件和原生代码变更不支持热更新只能整包更新这个限制在应用市场审核时要想清楚。第三安卓上架前需要做加固和签名各市场的加固方案不同签名证书建议用统一的keystore避免换证书导致无法覆盖更新。现在的安卓应用市场审核越来越严隐私政策弹窗、权限动态申请、应用内更新提示这些都要做合规。uniapp项目可以配合uni-popup做一个自定义隐私弹窗再通过条件编译控制只在App端弹出合规性强也方便维护。5. 常见问题与排查技巧实录5.1 日志不打印问题很多人在小程序端或App端调试时发现console.log完全不输出。常见原因有三个第一代码运行环境在压缩或混淆模式下可能被过滤第二App端真机调试需要开启“调试模式”并在HBuilderX控制台切换平台第三你在onHide或组件生命周期里打的日志可能被vconsole插件覆盖或忽略。我的排查习惯是先用console.log(JSON.stringify(data))打印结构化数据避免对象被浏览器控制台折叠成“无法展开”的状态。再不行直接用uni.showToast做一些关键节点的视觉提示确认逻辑走到了哪一步。H5端则直接打开浏览器DevTools看网络请求。5.2 video组件提示“未添加模块”这是几乎每周都会有人在群里问一遍的问题。现象是H5上视频播放正常打包到App后页面报错或视频区域空白。原因就是manifest.json里缺少VideoPlayer模块。检查步骤打开manifest.json切到App模块配置页找到VideoPlayer勾选保存后重新打包。如果你用的是离线打包或自定义基座还需要在原生工程里确认模块已经集成否则光勾选manifest也没用。这里特别提醒修改manifest之后HBuilderX会用“自定义调试基座”需要手动重新制作。我遇到过一次勾选了模块、重新云打包也没报错但真机仍然黑屏最后发现是自定义基座没有重新打包手机上跑的还是旧基座。所以改了manifest后一定要把基座和测试包同步更新。5.3 iOS Safari canvas导出白图这个坑在3.5里详细说过这里补充一个排查小技巧。如果导出白图先在导出前把canvas绘制的内容用uni.getImageInfo去读取一下或者在ctx.draw()回调里直接调用uni.canvasToTempFilePath看结果是否为空。如果确认绘制有内容但导出是白图试试在导出时给canvasId明确传值再检查destWidth和destHeight是不是设了奇怪的倍数。我还遇到过canvas在iOS上模糊的问题通常原因是canvas的实际像素尺寸和CSS尺寸不一致。导出时要让canvas的绘图缓冲区尺寸是显示尺寸的2倍或3倍按设备像素比导出图片的清晰度会好很多。画一个200px宽的海报canvas内部宽度设为600导出时destWidth为600这样iPhone上也不会模糊。5.4 热词问题速查表场景问题现象排查要点父传子子组件props不更新检查props命名是否用小写短横线确认父组件是否用:propName传值而非冒号后大写子传父事件不触发确认事件名是否全小写短横线检查$emit放在哪个生命周期里videoApp端黑屏manifest勾选VideoPlayer模块自定义基座是否重新打包canvasiOS导出白图等待绘制回调完成再加延迟导出检查canvas尺寸与实际绘制是否一致web-view收不到H5的回传消息小程序端postMessage在返回或分享时才触发App端需要evalJS桥接swiper图片变形设modeaspectFill让图片填充指示器和卡片宽度要按数据量动态计算map定位偏移确认坐标系是否统一为gcj02App端不同定位模块坐标系不同组件缓存页面状态丢失页面级keep-alive无效改用pinia恢复数据或组件级缓存原生插件打包失败检查manifest插件配置、签名证书和云打包渠道6. 组件化架构设计的几条实操建议如果项目已经从“单页验证”走向“多业务线并行”组件化架构的合理度会直接决定后续开发效率。第一条建立组件分层。基础组件层XbButton、XbInput、XbPopup只负责通用交互不承载业务。业务组件层商品卡片、订单状态条、支付面板组合基础组件并传入业务数据。页面层只负责布局和数据获取不抱团堆逻辑。这样改动业务时基础组件几乎不动。第二条用uni_modules管理公共组件。uni_modules是uniapp官方推荐的组件包格式目录结构规范支持发布到插件市场也支持项目内共享。项目里的通用组件放到uni_modules/xx-组件名下再配合easycom自动引入所有页面都能直接用不需要手动import这是效率提升非常明显的一个点。第三条每个组件都要写README。组件多了以后靠记忆根本撑不住。最低限度要在组件目录下放一个README.md写明props、events、slots、平台特殊处理哪怕只写要点也行。这段文字在半年后会救你一次。第四条组件内的平台适配代码要集中。条件编译可以写但不要散落得到处都是。在组件内部单独放一个platform.js或platform.vue片段把差异集中管理组件主体尽量保持逻辑清晰。比如一个视频组件play()方法在H5端调用DOM元素APIApp端调原生插件小程序端调video context三种实现都放在platform区块里内部再封装统一方法外部使用的时候不需要关心平台。7. 组件性能优化的一些个人经验做组件时如果只顾功能不管性能项目一大就会开始卡顿。这里分享几个上过线、压过真机的经验。长列表渲染。小程序和App端一次性渲染几百个组件节点即使逻辑不重也会明显卡顿。我的常规做法列表数据切片每次渲染30-50条滚动到底部再追加图片懒加载不要给所有图片设src。如果列表项里还有子组件、复杂交互优先考虑把它们拆成“轻组件”减少创建小程序原生节点时的开销。组件通信频率。千万不要在组件里watch一个props对象每次父组件setData或更新数据时小程序端会做全量diff。高频更新的数据比如进度条数值、图片加载状态尽量合并成一次更新或者使用nextTick延迟能显著减少卡顿感。图片资源处理。组件内部的图标、背景图尽量使用iconfont或者base64小图减少网络请求。大图统一走uniCloud或CDN并设置合理的widthFix和heightFix避免图片加载后撑破布局。8. 一个实际组件封装案例商品卡片说了这么多理论放一个实际封装过的商品卡片例子。需求是在首页推荐流、搜索结果页、订单页共用同一种商品卡片布局但点击行为和部分字段不同。组件名为ProductCard放在uni_modules/ProductCard/ProductCard.vue。props设计为product对象和showTag布尔值。内部只负责渲染商品图、标题、价格、销量标签点击时$emit(click, product)。推荐流里监听click跳详情订单页里监听click去再买一单逻辑全部在页面层处理。这样商品卡片组件本身完全无状态改一个样式三个页面同时生效。如果搜索页和首页的商品数据结构有差异组件内部做一层normalize映射把goodsName和title统一成name字段。这个映射逻辑放组件内部页面层不用关心数据结构差异。template view classproduct-card clickhandleClick image :srcproduct.cover modeaspectFill lazy-load / view classinfo text classname{{ product.name }}/text text classprice¥{{ product.price }}/text text v-ifshowTag classtag{{ product.tagText }}/text /view /view /template script setup const props defineProps({ product: { type: Object, required: true }, showTag: { type: Boolean, default: false } }) const emit defineEmits([click]) function handleClick() { emit(click, props.product) } /script页面里使用ProductCard :productitem show-tag clickgoDetail /这个例子看起来简单但它体现了组件封装的核心解耦、复用、对外边界清晰。实际项目中组件封装得越彻底跨端问题就越好解决。做uniapp组件开发这些年我最大的一个感受是组件本身不难难的是你永远不知道你的代码下一步会跑在哪个端上。它对开发者的要求是“带着平台差异意识去写每一行代码”而不是写完再回来补救。希望这篇文章的实操经验能帮你少踩一些不必要的坑。如果你在组件通信、video、canvas或者打包上遇到过其他稀奇古怪的问题不妨按照上面这些思路去排查多半能找到方向。
返回列表