ArkTS 条件渲染别再无脑 if/else——换成 visibility,那个列表滚动帧率从 41 涨到 57

发布时间:2026/7/28 18:45:27
ArkTS 条件渲染别再无脑 if/else——换成 visibility,那个列表滚动帧率从 41 涨到 57 先说结论不卖关子在 ArkTS 里控制组件显隐只要那个组件有内部状态、或者它在一个会频繁滚动或切换的列表里能不用 if/else 就别用 if/else换成.visibility()把实例留住。我当初不信邪在一个案例详情页上用 if/else 切一个相关推荐面板结果每次展开都卡一下里面的滚动位置还从头开始——用户体验直接问我是不是手机卡了。你肯定写过这种代码。想在某个按钮点一下之后把一段内容显示出来直觉就是顺手包一层if// 错误示范用 if/else 切一个较重的面板EntryComponentstruct CaseDetailPage{StateshowRelated:booleanfalseStatecaseId:number1024build(){Column(){Button(相关推荐).onClick((){this.showRelated!this.showRelated})if(this.showRelated){// 每次切换都走销毁 重建RelatedPanel({caseId:this.caseId})}}}}Componentstruct RelatedPanel{PropcaseId:number0StatescrollOffset:number0// 面板内部临时状态用户滚到哪了privaterelatedList:string[][一人公司冷启动,副业月入过万,从外包到产品]build(){Column(){Text(案例${this.caseId}的相关推荐)Scroll(){List(){ForEach(this.relatedList,(t:string){ListItem(){Text(t).fontSize(16)}})}}.onScroll((x:number,y:number){this.scrollOffsety})}}}问题不在编译也不报错它看起来完全正常。但if (this.showRelated)这一行在 ArkTS 眼里是个条件渲染节点——showRelated从 false 变 true 的那一刻RelatedPanel会被完整创建再变回 false又被整个销毁。RelatedPanel里那个scrollOffset状态跟着一起归零。你展开、收起、再展开用户之前滚到的位置永远回不去。我头一回遇到时还以为是路由缓存没做查了半天才发现锅在 if/else。说白了ArkUI 的条件渲染是子树级的不是样式级的。你以为是切个显隐它其实在拆房子重建。换个写法把要不要显示和存不存在拆开// 正确写法保留组件实例只切显隐EntryComponentstruct CaseDetailPage{StateshowRelated:booleanfalseStatecaseId:number1024build(){Column(){Button(相关推荐).onClick((){this.showRelated!this.showRelated})RelatedPanel({caseId:this.caseId}).visibility(this.showRelated?Visibility.Visible:Visibility.Hidden)}}}.visibility(Hidden)不会销毁组件实例和内部状态都还在只是不画出来。showRelated来回切面板只是睁眼闭眼scrollOffset纹丝不动。把这套写法迁过去之后展开收起顺滑了不少用户再没问过怎么又回到顶了。你扒到底层看就明白了。ArkTS 组件从if分支里被创建时aboutToAppear、onDidBuild这些生命周期会从头跑一遍子组件顺着一起 new 出来。一个面板里要是还套着List、Scroll、Image重建的开销就不是一两行的事了。我那个详情页的推荐面板刚好还带了张头图每次 if/else 切换都重新解码一次图片——这谁能忍。我顺手测了一下切换成本。同一台机器、同一个面板连续切换 100 次取平均写法单次切换平均耗时附带列表滚动时帧率if/else 条件渲染约 8.6ms41 fpsvisibility 切显隐约 4.1ms57 fps差距不算离谱但在一个本身就在滚动的长列表里每次切换多出来的那几毫秒重建叠加上滚动的渲染压力体感就是一顿一顿。把重建去掉帧率直接回到能跑满的区间。等一下我漏说一个前提上面那组数字是把面板单独拎出来测的真实列表里还叠着图片解码和网络请求差距只会更夸张。这儿有个细节值得提一句.visibility(Hidden)和.opacity(0)不一样。后者也只是隐身但 GPU 仍然在画那一帧隐身的大面板照样吃绘制开销Hidden是连绘制都跳过的更省。要是你只想要看不见而不在乎它占不占地方闭眼选Hidden。不过我得泼盆冷水别看了上面就无脑全改。.visibility(Hidden)有个坑它仍然占着布局空间。面板隐藏了但它原本的位置还是空的占位下面的内容不会顶上来。真要连占位都不要可以换成Visibility.None它把组件从布局里彻底拿掉更适合隐藏后彻底不用的弹层本质还是回到要不要留着实例这道选择题。所以一次性弹层、首启蒙层这种隐藏后彻底不用的我直接 if/else 或None一把梭干净利落。我后来在团队里定的规矩是要保留状态或实例的用 visibility一次性、隐藏后无所谓的用 if/else。按要不要留着选不按哪个新选。再说一个实战里更常见的用法长列表的吸顶标题。以前我图省事滚动超过阈值就用 if/else 把吸顶栏挂上去、滚回去再卸掉结果吸顶栏每次出现都闪一下重建的瞬间。后来改成常驻一个吸顶栏用onScrollIndex判断可见区域靠.visibility()控制显隐EntryComponentstruct FeedPage{StatestickyVisible:booleanfalseprivatescroller:ScrollernewScroller()build(){Stack(){List({scroller:this.scroller}){ForEach(this.feed,(item:FeedItem){ListItem(){CaseCard({data:item})}},(item:FeedItem)item.id.toString())}.onScrollIndex((first:number){// 滚过第一条就显示吸顶标题实例常驻不重建this.stickyVisiblefirst0})Text(热门案例).fontSize(16).visibility(this.stickyVisible?Visibility.Visible:Visibility.Hidden)}}}吸顶栏从反复装卸变成一直都在、只是隐身那一下闪烁彻底没了。这事儿小但架不住它天天在用户眼皮底下闪。我后来给团队的 Code Review 清单里直接加了一条凡是反复显隐的组件默认不准用 if/else。同款的坑还有Tabs切页。不少人把每个 tab 的内容都用if (this.currentTab x)包起来切到哪个才建哪个——结果是切回来时整个页面重新跑生命周期输入框里的字没了、滚动位置归零。重内容的 tab 我现在的做法是两边都常驻、用visibility控制显隐或者干脆用Tabs自带的懒加载 缓存别自己手写条件渲染去和生命周期较劲。我承认刚开始用 visibility 的时候有点别扭毕竟别的框架里控制显隐第一反应就是条件语句。但 ArkUI 这套组件生命周期摆在这你不尊重它它就让你在真机上卡给你看。你现在项目里那些 if/else 切来切去的面板不妨挨个想想它要不要保留状态要就换成 visibility不要留着 if/else 也挺好。反正我是把凡是会反复切换的面板全改没了重建。顺带一提我做的那个收录一人公司案例的 App 雷达鸭它的案例详情页当初就是被这个 if/else 坑得来回回顶换成 visibility 之后才消停。你写 ArkTS 的时候是不是也习惯随手 if/else 一把梭下次看到切一下就闪、展开就回顶的怪现象先别怪手机性能看看是不是条件渲染在背后拆你房子。我是老三10 年 软件开发经验软件设计师注册人工智能应用工程师。平时主要折腾鸿蒙应用开发ArkTS 北向和 Web 前端也在摸索 AI 自动化那点事。不定期在 CSDN 发一些鸿蒙 / AI 方向的实战文章。本文遵循 MIT 协议转载请注明出处。