ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

React Native鸿蒙开发:Shimmer闪光效果实现与性能优化实战

React Native鸿蒙开发:Shimmer闪光效果实现与性能优化实战 做了几年 React Native 业务开发最近被鸿蒙化适配问题找上门的时候第一反应其实是有点懵的。跨端框架我写过不少从早期的 Cordova 到后来的 RN、Flutter 都碰过但真正把自己已经上线的 RN 工程跑在 HarmonyOS 上还要处理启动白屏、动画性能、上架材料这些连环坑确实是一段绕不开的经验。这篇博文不聊虚的只讲我实际做“React Native 鸿蒙跨平台开发”中完成的一个小功能Shimmer 闪光效果看完你也能在自己的项目里快速落地。Shimmer 效果说白了就是那种“加载骨架屏上有一条高光扫过来扫过去”的视觉反馈前端同学一般叫它闪烁占位、闪光条电商 App 加载商品列表时最常见。为什么拿它当基础入门的例子因为 Shimmer 麻雀虽小五脏俱全涉及动画循环、组件封装、性能优化还要考虑在鸿蒙端 react-native 适配层的兼容性。把这个功能跑通了你对“RN 在鸿蒙上到底能不能用、性能和坑在哪里”就有了直观判断。这篇文章适合正在做 RN 鸿蒙适配、想给现有 RN 项目增加骨架屏效果或者纯粹想了解鸿蒙跨端开发实际体验的开发者。1. React Native 鸿蒙适配现状与方案选型1.1 鸿蒙上跑 RN到底跑的是什么先把一个最基础的问题说清楚鸿蒙上跑 React Native并不是直接把原来跑在 Android 上的 APK 拿过来改两行就行。HarmonyOS 从 NEXT 版本开始不再兼容 Android APK这意味着原本 RN 依赖的 Android 原生桥接层、so 库、Surface 渲染链路在鸿蒙上都不可用了。RN 在鸿蒙上的运行依赖的是社区和设备厂商共同维护的“鸿蒙化适配层”简单理解就是有人把 RN 的 C 核心、JS 引擎、渲染管线对接到了鸿蒙的 ArkUI 组件系统上让 RN 的虚拟 DOM 最终能映射成鸿蒙原生组件。实际操作中我用的是社区维护的 react-native-harmony 仓库再配合特定版本的 RN 发行渠道。这个适配层目前还在快速迭代但基础功能已经能支撑真实业务开发。你在规划项目时要先确认三件事RN 版本和鸿蒙 SDK 版本的对应关系、三方原生依赖是否有鸿蒙实现、以及需要哪些额外补丁。千万不要以为把 Android 版 RN 版本号一升鸿蒙端就自动兼容这个坑我踩过。1.2 为什么这个阶段值得做 RN 鸿蒙开发可能有人会问既然鸿蒙有自己的 ArkUI为什么不用 ArkTS 重写这个问题我一开始也纠结过。后来想明白了核心逻辑就三点第一存量业务成本。团队里已经有一套用 RN 写的跨端业务覆盖 iOS 和 Android现在要上鸿蒙如果全部用 ArkTS 重写等于把业务逻辑和 UI 层重新来一遍周期和 bug 量都不可控。用 RN 鸿蒙化适配JS 业务代码可以最大程度复用原生层只做桥接适配。第二团队技术栈延续。很多前端团队没有 ArkTS 经验但 React 经验很丰富。让一个 React 团队快速产出鸿蒙版本RN 适配层是成本最低的路径。在招聘市场上RN 开发者也比 ArkTS 专项开发者更容易招。第三动态化能力。RN 的 JavaScript 代码可以走热更新通道虽然鸿蒙上对热更新有比较严格的审核要求但相比纯 ArkUI 原生应用只能走应用市场发版RN 在灰度发布和动态化上还是灵活不少。当然如果你的项目是全新项目、团队也不背历史包袱那直接用 ArkUI 当然更“鸿蒙原生”。我这里讨论的是实际场景中占比很大的存量跨端项目怎么处理。1.3 Shimmer 方案选型自研还是用第三方库做 Shimmer 效果网上能搜到不少现成库比如 react-native-shimmer、react-native-flash-animation 之类的。但在鸿蒙适配场景下我的建议是基础入门阶段最好自己用 RN 内置 Animated API 写一个不要一上来就引第三方原生库。原因很简单。RN 的第三方库只要不是纯 JS 实现基本都带了 Android 和 iOS 的原生代码。这些库有没有做鸿蒙适配完全是未知数。我试过在鸿蒙工程里引入某个社区星光效果库Android 上跑得好好的鸿蒙端编译阶段直接报原生模块找不到最后还得自己改源码折腾半天不如自己写。自研 Shimmer 用 RN 内置的 Animated API 就能实现不依赖任何额外原生模块这意味着在鸿蒙适配层里它的兼容性风险最小。另一个好处是你可以完全控制效果细节高光颜色、移动速度、扫光范围、循环次数都能通过组件参数暴露出去后面换主题、调品牌色都很方便。2. Shimmer 闪光效果的实现原理与组件设计2.1 拆开看 Shimmer它到底在做什么Shimmer 的视觉本质是在等待内容加载时用一块颜色的占位区域盖住真实内容然后让一条高光在这个占位区域上循环扫过塑造一种“数据正在加载”的动态隐喻。很多骨架屏方案还做了渐变边缘高光不是硬邦邦的矩形而是从透明慢慢过渡到半透明白再回到透明看起来更柔和。拆成技术要素Shimmer 只需要三个东西一个可以循环执行的动画值、一个把动画值映射成位置变化的插值函数、以及一个用作高光条的视图。在 RN 里这三个东西分别对应 Animated.Value、interpolate 和 Animated.View。整个组件可以用一个外层 View 包裹子组件加载中时盖上一层遮罩遮罩上再放一条会平移的高光条。为什么强调“一个动画值就够了”因为闪光效果的本质是一个一维运动。不管你怎么加渐变、加模糊、加颜色变化驱动整个效果的核心变量只有一个时间。用 Animated.Value 驱动时间进度再用 interpolate 映射到坐标为高光条的位置所有视觉效果都能基于这个变量推导出来。这样设计的好处是逻辑简单动画线程只需要处理一个数值的变化性能和可控性都好。2.2 动画循环Animated.loop 和 useNativeDriver 怎么选RN 里做循环动画最标准的方式是 Animated.loop 配合 Animated.timing。Shimmer 不需要缓动函数匀速扫光更符合真实灯带扫过的感觉所以用 Easing.linear。如果希望高光来回扫可以配合 Easing.inOut 或自定义映射方向但默认情况下我建议“从左到右扫完一遍从头开始”实现最简单视觉效果也不差。这里有个关键选择useNativeDriver 要不要开。在 Android 和 iOS 上只要动画只驱动 transform 和 opacity就可以开 true让动画跑在原生 UI 线程避免 JS 线程卡顿影响动画流畅度。鸿蒙适配层目前对 useNativeDriver 的支持也在逐步完善transform 这类原生驱动属性一般没问题。我的实操习惯是先开 true如果鸿蒙端出现动画不生效或者闪退再降级成 false。降级只是让动画回到 JS 驱动功能还在只是性能差一些。2.3 组件 API 设计怎么让 Shimmer 通用起来写组件不能只写死一条高光要考虑业务复用。我设计的 Shimmer 组件对外暴露五个核心参数参数类型默认值说明visiblebooleantrue是否显示闪光效果加载完成后置为 falsedurationnumber1200单次扫光时长单位毫秒baseColorstring#E8E8E8遮罩底色也就是占位区域的灰色highlightColorstringrgba(255,255,255,0.4)高光颜色建议半透明白styleViewStyle无外层容器样式控制宽高和圆角用法上你只需要把真实内容当成 children 传进去加载中包一层 Shimmer 即可。加载完成后把 visible 设为 false遮罩消失真实内容露出来。这样 Shimmer 更像一个“加载包装器”而不是特定组件任何页面都能用。注意highightColor 不要用完全不透明的白色否则扫光会显得很“脏”。半透明白配合底色的叠加才有自然的光泽感这是我调了好几次参数才得到的经验。2.4 为什么不优先推荐 Reanimated 和 LinearGradient说到动画RN 生态里还有 React Native Reanimated 性能更猛react-native-linear-gradient 做渐变效果更漂亮。但在鸿蒙适配这个前提下这两个库我都暂时不推荐作为基础方案。Reanimated 需要原生渲染器支持鸿蒙适配层的兼容版本要单独找LinearGradient 也是原生组件鸿蒙端能不能正常渲染渐变是个未知数。基础版用 Animated API 加遮罩和高光条相当于把依赖降到最低。如果后续鸿蒙适配层成熟了再考虑替换成 Reanimated LinearGradient 也不迟。做跨端开发的第一原则永远是“先跑通再优化”尤其面对一个还在快速迭代的适配层能少引一个原生依赖就少引一个。3. 完整实现流程与核心代码3.1 环境准备鸿蒙 RN 工程长什么样开始写代码前先确认你的 RN 工程已经能在鸿蒙设备或模拟器上跑起来。具体环境配置比如 DevEco Studio 版本、鸿蒙 SDK、react-native-harmony 的集成步骤建议直接参考适配仓库的 README版本匹配是关键。我只提醒几个容易踩的点Node 和 Java 环境要满足鸿蒙构建要求Java 版本别太高也别太低我这边用 JDK 17 比较稳。鸿蒙侧工程和 RN 目录要用相对路径关联路径里不要有中文字符。构建之前先执行一次 npm install确保 node_modules 完整鸿蒙侧会读取 RN 依赖。首次在鸿蒙模拟器运行资源同步会比较慢属正常现象耐心等。我自己实际跑通的环境组合是React Native 0.72 系 鸿蒙 SDK 5.0 对应版本。你如果用的是更高版本的 RN需要留意适配层的支持情况社区一般会在 release note 里写明。3.2 从零实现 Shimmer 核心组件下面是我在实际项目中用的 Shimmer 组件代码做了精简但核心逻辑完整。建议你先照着跑通再根据业务需要改参数。import React, { useEffect, useRef } from react; import { Animated, Easing, StyleSheet, View, ViewStyle, } from react-native; interface ShimmerProps { visible?: boolean; duration?: number; baseColor?: string; highlightColor?: string; style?: ViewStyle; children?: React.ReactNode; } const Shimmer: React.FCShimmerProps ({ visible true, duration 1200, baseColor #E8E8E8, highlightColor rgba(255, 255, 255, 0.4), style, children, }) { const progress useRef(new Animated.Value(0)).current; useEffect(() { if (!visible) { progress.setValue(0); return; } const animation Animated.loop( Animated.timing(progress, { toValue: 1, duration, easing: Easing.linear, useNativeDriver: true, }) ); animation.start(); return () { animation.stop(); }; }, [visible, duration, progress]); const translateX progress.interpolate({ inputRange: [0, 1], outputRange: [-100%, 100%], }); return ( View style{[styles.container, style]} {children} {visible ( View style{[StyleSheet.absoluteFill, styles.overlay, { backgroundColor: baseColor }]} Animated.View pointerEventsnone style{[ styles.highlight, { backgroundColor: highlightColor, transform: [{ translateX }], }, ]} / /View )} /View ); }; const styles StyleSheet.create({ container: { overflow: hidden, }, overlay: { justifyContent: center, }, highlight: { width: 60%, height: 100%, borderRadius: 8, }, }); export default Shimmer;这里有一个视觉上的细节我让高光条宽度为容器宽度的 60%从 -100% 移到 100%当它从左边界移动到右边界时视觉上确实完成了“一次扫光”。如果你把高光条设成 100% 宽度再移动效果就很差因为整个占位区域同时被高光盖住了看不出“扫”的感觉。这个参数需要结合你实际内容的形状调整。3.3 在业务页面里接入 Shimmer组件写好了业务侧使用非常简单。以商品列表骨架屏为例加载时把一行商品占位卡塞进 Shimmer等数据回来后切换成真实列表。const [loading, setLoading] useState(true); const [products, setProducts] useState([]); useEffect(() { fetchProducts() .then((data) setProducts(data)) .finally(() setLoading(false)); }, []); return ( View style{styles.page} {loading ? ( Shimmer style{styles.cardPlaceholder} View style{styles.cardInner} / /Shimmer ) : ( ProductList data{products} / )} /View );注意Shimmer 的 children 只是占位用的视觉元素不需要把真实内容写进去。真实内容加载完成后整体替换这样数据流最简单也避免出现真实内容和遮罩错位闪烁的问题。如果你要做一个列表页同时显示多个骨架项建议循环渲染多个 Shimmer每个的高度和宽度模拟真实条目的比例。这里有个经验同一个页面里如果有多个 Shimmer 实例动画会各自启动看着虽然不是完全同步但反而更自然。如果强迫它们严格同步反而像复制粘贴的假页面。3.4 进阶让高光变成渐变扫光基础版的高光条是一整块半透明色视觉上偏“硬”。如果你追求更柔和的效果需要高光从中间亮、两边淡。React Native 内置没有渐变组件在不引第三方库的情况下可以用一个取巧方案用三层层叠的 Animated.View 模拟渐变边缘。具体做法是中间层是宽度 30% 的半透明白左右两层分别是透明度递减的细条三个层共用同一个 progress 动画值同步移动。因为它们的移动速度一致人眼会把三层看成一个整体视觉上就有“中间亮、两边暗”的渐变效果。这个方法虽然听起来“土”但完全不依赖原生组件鸿蒙端稳定性很好。const highlightWidth 60%; const layerCommon { position: absolute as const, left: 0, top: 0, height: 100%, width: highlightWidth, }; const layerStyle (opacity: number, transform: any) ({ ...layerCommon, backgroundColor: highlightColor, opacity, transform, });用三个 Animated.View 叠加的代码稍微繁琐一些但胜在纯 JS 实现鸿蒙端不会有兼容问题。在实际项目中我最终上线的是这版渐变扫光用户可以自定义配置层数。作为基础入门你先把基础版跑通再按需升级就够。4. 鸿蒙端实际运行问题与排查技巧4.1 启动白屏React Native 鸿蒙上最大的拦路虎搜索热词里“react native 启动白屏”排在很前面这确实是 RN 鸿蒙化绕不开的痛。白屏从现象上看是应用启动后长时间只有一个空白背景JS 代码始终没渲染出来。结合我自己的调试经验常见原因有这么几类首帧时间太长。鸿蒙端跑 RN 需要先加载 JS Bundle还要初始化鸿蒙侧渲染引擎手机性能一般的话白屏个两三秒很常见。解决方案是启动时先渲染一个原生壳子页等 JS Bundle 加载完成后再切入 RN 页面。Bundle 资源路径配置错误。鸿蒙端读取 JS Bundle 的路径和 Android 不一样如果 base path 配错了JS 根本加载不进来自然一片白。检查鸿蒙工程 assets 目录里有没有正确放入 bundle 文件。原生模块没注册。如果工程里引了一些原生模块但没有在鸿蒙侧注册启动时到对应桥接会失败但不一定会崩溃可能表现为白屏或卡在某个页面。排查时看日志有没有 Unimplemented 或 Module not found 之类的报错。渲染线程和 UI 线程同步问题。鸿蒙适配层还在迭代极少数情况下会出现首帧渲染卡死升级适配层版本或者调整 RN 版本能解决。排查白屏问题我的习惯是先开 Metro 服务器跑 debug 包看 JS 端有没有异常报错。如果 debug 包正常release 包白屏基本就是 bundle 打包或资源路径问题。如果 debug 包也白屏优先查原生侧日志和模块注册。4.2 动画卡顿JS 线程被打满动画跟着掉帧Shimmer 本身很轻量正常情况不会卡。但如果你在同一个页面里放了十几个 Shimmer每个都启动一个独立的 Animated.loopJS 线程压力会快速上升。尤其鸿蒙适配层刚起步JS 和原生之间的消息传递效率还没到最优问题会被放大。我的做法是在列表这类场景里不要渲染几十个独立 Shimmer而是用 FlatList 只渲染可视区域内的骨架项每页最多 8 到 10 个。另一个技巧是所有 Shimmer 实例共享同一个动画值或者用一个外层 Animated.Value 控制统一循环避免各自创建定时器。用同一天动画值还带来一个额外好处整页骨架屏的扫光节奏完全同步视觉上更整齐这是很多大厂骨架屏设计稿里会要求的。如果 still 卡顿下一步把 useNativeDriver 打开确保动画跑在原生线程。实测下来在鸿蒙模拟器上 useNativeDriver 对 transform 的支持还是可靠的性能提升明显。4.3 鸿蒙端常见的 JS 兼容性差异除了启动和动画开发中还容易碰到一些 JS 运行时的兼容差异。鸿蒙的 JS 运行时和 Android 上默认的 Hermes 引擎有所不同一些 API 表现会出现差异比如console 对象在 release 包中被裁剪导致线上排查日志看不到。Intl 可能不支持全部的语言区域配置涉及格式化日期、金额要注意。某些第三方纯 JS 库如果用了比较新的 ES 特性鸿蒙运行时不一定都支持构建时建议用 babel 做降级。fetch 的网络栈行为和 Android 不完全一致超时设置、请求头大小限制都要实测。这些差异不影响 Shimmer 本身但会影响你整个 RN 鸿蒙项目的运行体验。我的建议是在项目初期就建立一套鸿蒙专项的自测用例重点覆盖网络请求、图片加载、动画、存储这些基础能力。4.4 常见问题速查表问题现象可能原因排查思路启动白屏JS Bundle 未加载或路径错误先跑 debug 包检查 Metro 连接Shimmer 不移动useNativeDriver 在鸿蒙上不支持改成 false再观察效果动画掉帧严重页面 Shimmer 实例过多用 FlatList 复用共享动画值高光颜色发灰baseColor 和 highlightColor 搭配不当把高光调成半透明白再试组件 unmount 后动画仍在没有清理 Animated.loopuseEffect 里调用 animation.stop()鸿蒙构建失败原生依赖缺少鸿蒙实现移除该原生依赖或用 JS 替代这张表是我实际开发中遇到频率最高的问题集合你可以直接当作排查手册用。遇到新问题优先在适配仓库的 issue 和社区里搜关键词很多坑都是公开记录过的。5. 从开发到上架鸿蒙应用交付经验5.1 鸿蒙应用上架需要准备哪些东西开发完功能下一个绕不开的环节就是上架。搜索热词里“鸿蒙应用上架需要写哪些东西”热度很高说明大家都卡在这一步。按我实际走下来的经验鸿蒙应用上架准备的材料大致分为这几块应用基本信息应用名称、图标、一句话简介、详细描述、分类、截图和预览图。截图要覆盖不同尺寸设备包括手机和平板。开发与签名信息需要申请鸿蒙开发者账号并配置签名证书。和 Android 的 keystore 类似鸿蒙也有自己的证书体系和配置文件签名不符直接无法构建上架包。隐私政策应用如果涉及收集用户信息必须在应用内和上架后台同时提供可访问的隐私政策链接内容要覆盖收集了什么数据、怎么用、用户有哪些权利。权限声明每个敏感权限如相机、位置、麦克风都要在后台做用途说明说明文字会被审核人员逐条核对。软件著作权部分类目的应用在正式上架审核时需要提供软著证明如果没有尽早准备会拖慢审核流程。测试报告后台会要求填写软件功能测试报告自己测完填写即可如果功能复杂建议提前做一份完整的测试用例记录。一个容易忽略的点是应用内的“用户协议”和“隐私政策”入口必须明显。很多应用只在注册页面放链接审核时被驳回要求补充。我后来养成的习惯是启动页或首页底部常驻一个“关于”入口里面同时放用户协议和隐私政策这是最稳妥的做法。5.2 签名、版本管理和灰发策略鸿蒙上架的签名过程可以通过 DevEco Studio 自动生成也可以用命令行工具但要注意签名文件和配置文件必须和提交后台时登记的证书信息一致。如果团队里有负责 iOS 或 Android 的同事可以类比一把鸿蒙的证书体系类似 iOS 的描述文件配置错了连打包都过不了。版本管理上鸿蒙应用同样遵循语义化版本号规则。因为我用的是 RNJS 业务代码更新比较快所以我在鸿蒙侧把版本号拆成“原生壳版本”和“JS 包版本”两层原生壳走正常上架流程JS 包走内部发布通道这样业务更新不需要每次都在应用市场发版。当然热更新在不同渠道的合规要求不一样你要确保自己用的更新通道符合平台规则。灰度发布是另一个建议。第一次上架的鸿蒙应用别一上来全量放量。我之前的做法是先在应用市场后台设置 5% 到 10% 的灰度比例连续观察两三天看崩溃率和性能监控指标确认没有明显问题后再放量。RN 鸿蒙适配层还不够成熟灰度发布是给自己留一条安全缓冲带。5.3 崩溃监控与线上问题定位RN 项目上线后最怕什么怕线上崩溃但拿不到有效堆栈。鸿蒙适配层加上 RN 的 JS 层两套栈混在一起排查难度比纯 Android 高不少。所以我建议在项目初期就接好鸿蒙侧的崩溃采集能力同时把 JS 层日志上传到自己的服务端。一种比较笨但有效的方法是在 JS 层全局拦截 console.error、unhandledrejection把这些异常信息带上设备信息、页面信息、时间戳统一上报到服务端。这样即使原生侧的堆栈信息拿不到至少你能知道是哪个 JS 操作触发了问题。我在 Shimmer 组件里也加了这类日志一旦出现动画异常能快速定位是组件参数问题还是适配层问题。线上问题定位还有一个技巧在灰发阶段把“日志上报开关”默认打开等全量后再按需关闭。这样即便出现偶现问题也能从历史日志里回溯现场而不是等用户去投诉。5.4 团队协作与文档沉淀最后聊点工程化之外的经验。鸿蒙 RN 开发目前还属于早期团队里每个人的理解都可能有偏差所以文档沉淀特别重要。我们项目里维护了一份“鸿蒙适配清单”内容涵盖RN 版本和鸿蒙适配层的对应版本列表已确认可用的原生依赖清单已排查过的问题和解决方案上架和签名操作的步骤文档灰发和线上排查的 SOP每次遇到新问题解决完就更新这份文档。一个月下来这份清单就成了团队里最值钱的知识库。后面新同学加入让他先过一遍清单能省下大量踩坑时间。Shimmer 这个“小功能”虽然不起眼但从它出发我把 RN 鸿蒙开发的全链路都走了一遍这些经验的沉淀价值远大于功能本身。最后再分享一个实际运行中的小技巧如果你的页面 Shimmer 效果在鸿蒙真机上看起来偏慢别只调 duration先看看是不是整个页面 JS 线程负载太高。把列表容器换成 FlatList、避免过度渲染、减少不必要的 setState这些优化对动画流畅度的影响经常比直接调耗时参数更明显。做跨端开发性能问题永远是全局问题不是单个组件的问题。
返回列表