React Native 全能商城应用实现与鸿蒙跨端适配深度解析
做移动端这么多年,商城类App我一直觉得是最能检验一个跨端方案成色的场景。商品瀑布流、分类级联、购物车状态同步、订单倒计时、推荐流无限加载,再加上登录、支付、推送这些系统级能力,随便拎一个模块出来都能写好几篇踩坑记录。最近我把一个完整的React Native商城项目从双端扩展到了鸿蒙NEXT,整个过程踩了不少坑,也沉淀了一套可以复用的适配方案。这篇就围绕整个项目的架构设计、核心模块落地、鸿蒙适配全流程以及性能优化四个部分展开,把我实际跑通的方案写出来,给正在做RN跨端或者正准备接鸿蒙的团队一个参考。
先说下文章适合谁看。如果你只是用RN写过几个demo页面,那这篇文章可以帮助你理解一个真实商城项目的完整结构;如果你已经用RN做过完整业务,正准备接入鸿蒙,那第三部分的适配流程和第四部分的坑位清单可以直接省你一周时间。全程不涉及具体公司业务代码,都是通用的工程实践,照着思路走基本都能落地。
1. 项目整体设计与技术选型思路
1.1 为什么是React Native而不是原生或Flutter
这个项目最初就是奔着“一套代码,多端运行”去的。商城类业务有个特点:运营活动频繁,页面迭代速度快。原生双端并行开发的成本在中小团队里很难承受,而Flutter虽然渲染性能好,但Dart语言生态在国内电商场景里还不够成熟,很多现成的支付、推送、统计SDK都是先给RN和原生留接口。再加上团队本身有前端背景,React Native可以复用大量前端状态管理和组件化经验,所以最终选了RN作为主框架。
还有一个很实际的原因:RN的热更新机制。商城活动页经常要临时改UI、调文案,如果每次都要走原生审核流程,活动基本就凉了。RN配合CodePush这类方案,可以在不发版的情况下更新JS Bundle,这个能力在电商场景里几乎是刚需。Flutter虽然也有热更新方案,但受平台限制更大,稳定性也差点意思。
当然,RN的短板我们心里也有数:启动性能、长列表渲染、内存占用,这些在商城这种重度页面场景里都会被放大。但这些问题不是无解的,后面第四部分会详细讲我们是怎么做针对性优化的。选型本身没有绝对的对错,关键是明确核心诉求是什么。这个项目的核心诉求就是“快”——开发快、迭代快、上线快,所以RN是最合适的答案。
1.2 技术栈与工程结构设计
先列一下整个项目的核心技术栈,这些依赖都是我们实际跑过生产环境的版本,比较稳定:
| 模块 | 选型方案 | 说明 |
|---|---|---|
| 核心框架 | react-native 0.72.x | 稳定版本,鸿蒙适配社区支持较好的是0.72这条分支 |
| 导航 | react-navigation v6 | 商城多级页面栈管理,tab导航+stack导航组合 |
| 状态管理 | redux-toolkit + redux-saga | 购物车、订单状态机、用户信息这些全局状态统一管理 |
| 网络请求 | axios + 自研拦截器 | 统一处理token刷新、错误码映射、埋点上报 |
| 列表渲染 | FlatList + 自研分页封装 | 商品瀑布流和推荐feed流都基于FlatList改造 |
| UI组件库 | react-native-paper + 自研业务组件 | 基础控件用现成库,业务组件全部自研 |
| 原生桥接层 | 统一封装NativeBridge | 登录、支付、推送、统计全部走桥接抽象层 |
工程目录结构上,我采用的是“按feature划分+共享层”的方式。核心目录大概长这样:
code复制src/
├── features/ # 业务模块,每个模块独立目录
│ ├── home/ # 首页
│ ├── category/ # 分类导航
│ ├── cart/ # 购物车
│ ├── order/ # 订单模块
│ ├── user/ # 个人中心
│ └── shop/ # 商家模块
├── shared/ # 跨模块共享层
│ ├── components/ # 通用业务组件
│ ├── hooks/ # 自定义hooks
│ ├── utils/ # 工具函数
│ └── constants/ # 常量配置
├── bridge/ # 原生桥接抽象层
│ ├── auth.ts # 登录桥接
│ ├── payment.ts # 支付桥接
│ ├── push.ts # 推送桥接
│ └── tracker.ts # 统计桥接
├── store/ # 全局状态管理
├── api/ # 接口请求层
└── App.tsx # 应用入口
这个结构的核心思想是:业务模块之间尽量不要直接引用,需要通过shared层或store进行通信。比如购物车模块要弹出登录框,不会直接调用user模块的组件,而是通过全局事件总线或者redux action触发,这样后续拆包、降级、模块复用都会方便很多。
1.3 提前预留鸿蒙适配层的必要性和设计思路
这个项目立项的时候鸿蒙NEXT还没有完全铺开,但我们当时就做了一个比较重要的决策:所有原生能力调用不允许在业务代码里直接出现,必须经过bridge层的统一封装。这个决策在后来适配鸿蒙的时候发挥了巨大作用。
bridge层的设计思路很简单,类似依赖倒置原则。业务侧只依赖抽象接口,不关心底层实现:
typescript复制// bridge/auth.ts
export interface AuthBridge {
login(type: LoginType): Promise<LoginResult>;
logout(): Promise<void>;
getUserInfo(): Promise<UserInfo>;
}
// 业务代码里只调用接口
import { authBridge } from '@bridge/auth';
const result = await authBridge.login('wechat');
至于底层具体走的是微信SDK、支付宝SDK还是鸿蒙的Account Kit,全都在bridge实现层处理。这样适配鸿蒙的时候,我们只需要新增一套鸿蒙平台的实现,替换注入即可,业务代码完全不用动。这个抽象层前后花了两三天时间梳理,但至少在鸿蒙适配阶段帮我们省下了至少两周的重复改造工作。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 商城核心模块的实现与落地
2.1 分类导航与多级级联的实现方案
分类页是商城App里面看起来简单、做起来琐碎的模块。最常见的交互就是左侧一级分类列表,右侧二级分类的网格或者列表。这个功能的技术难点不在于UI,而在于数据层级组织和滚动性能。
数据模型上,我们采用的是扁平化+parentId关联的方式,而不是嵌套JSON。好处是数据更新和缓存处理更灵活,reducer处理起来也简单:
typescript复制// 扁平化的分类数据结构
interface CategoryNode {
id: string;
parentId: string | null;
name: string;
icon: string;
level: number; // 1级、2级、3级
children: string[]; // 子节点id数组
sortOrder: number;
}
左侧一级分类用普通的FlatList就行,几十个item根本没什么压力。关键在右侧区域,二级、三级分类会出现嵌套滚动。我们的做法是:右侧整个区域是一个垂直FlatList,每个二级分类作为一个section,里面再嵌套一个横向网格(用FlatList的numColumns实现),三级分类通过“查看全部”跳转到独立页面,不做多层嵌套。
很多初学者在实现分类页时会直接用一个ScrollView包一个垂直的FlatList,这是典型的性能坑,会导致VirtualizedList的警告并且滚动卡顿。正确做法是保持最外层只有一个数据列表,所有分组都作为list item处理。
顺带说一下热搜里那个“react native 如何实现循环滚轮”。循环滚轮在商城场景里通常用于时间选择器、省市区选择器这类控件。RN里实现方案其实很简单:把滚轮内容做成一组item循环渲染,监听滚动位置计算当前选中项。核心代码思路是给数据集首尾各加一个“哨兵item”,滚动到位时直接跳转到中间位置,形成视觉上的无限循环。这样用户往上滑到头还能继续滑,不会卡在边界。如果是日期这类有规律的数据,直接动态生成前后若干组数据就能实现循环效果。
2.2 商品列表、搜索筛选与图片加载优化
商品列表是商城App的命脉模块,这里是性能优化投入最多的地方。我们的主列表场景是首页推荐feed流+搜索结果列表+分类商品列表,全都是无限滚动。
第一,分页逻辑要封装成统一的hook,不能每个页面各写一套。我封装了一个usePagination,接收请求函数和参数,返回列表数据、加载状态、刷新方法、加载更多方法。内部统一处理了防重复请求、列表合并、空态/错误态展示:
typescript复制function usePagination<T>(
fetcher: (params: PageParams) => Promise<PageResult<T>>,
deps: any[]
) {
const [list, setList] = useState<T[]>([]);
const [page, setPage] = useState(1);
const [loading, setLoading] = useState(false);
const [refreshing, setRefreshing] = useState(false);
const [hasMore, setHasMore] = useState(true);
const loadMore = useCallback(async () => {
if (loading || !hasMore) return;
setLoading(true);
try {
const res = await fetcher({ page: page + 1, pageSize: 20 });
setList(prev => [...prev, ...res.list]);
setPage(page + 1);
setHasMore(res.list.length >= 20);
} finally {
setLoading(false);
}
}, [page, loading, hasMore, fetcher]);
const refresh = useCallback(async () => {
// 重置列表,重新拉第一页
}, [deps]);
return { list, loading, refreshing, hasMore, loadMore, refresh };
}
搜索筛选这块,关键词搜索走远程接口没有问题,但价格区间、品牌筛选这类条件,我们做的是本地过滤+远程分页的结合方案。用户点击筛选条件时,不立即刷新列表,而是先把条件暂存到筛选栏,点击“确认”后统一拼参数请求接口。这样避免每次点击一个筛选标签就发一次请求,服务端压力小很多。另外所有搜索输入框都做了300ms防抖,别小看这个细节,高峰期能减少大概40%的无效请求量。
商品图片优化是个老生常谈但必须重视的问题。我们在服务端上传图片时会生成多种尺寸,客户端按列表缩略图、详情大图、横幅banner等场景分别取不同的URL。同时图片缓存采用了react-native-fast-image,它基于原生图像加载器实现,内存占用比RN默认的Image组件小得多。实测在商品列表滑动的场景下,用fast-image比默认Image的内存占用降低约50%,帧率也稳定得多。
2.3 购物车与订单状态机的状态管理实践
购物车是所有电商App里状态最“散”的业务模块。选中状态、数量变化、过期失效、优惠计算,这些状态散落在各个页面里,如果不做统一管理,很容易出现“列表里改了数量,结算页不刷新”这种低级bug。
我们选择了redux-toolkit作为全局状态管理,购物车模块专门维护一份cartSlice:
typescript复制const cartSlice = createSlice({
name: 'cart',
initialState,
reducers: {
addItem(state, action) {
// 同商品且同规格则数量叠加,否则新增
},
updateQuantity(state, action) {
// 更新数量并重新计算选中状态
},
toggleSelect(state, action) {
// 切换单个商品选中状态,重新计算全选状态
},
clearChecked(state) {
// 清除已选商品(下单成功后触发)
}
}
});
购物车数据还有一个关键问题:本地持久化与多端同步。我们每次业务操作后都会把购物车数据序列化写入AsyncStorage,同时后台静默同步到服务端。用户换设备登录后,优先拉取服务端数据,再与本地合并。这里有个细节:合并策略不是简单的覆盖,而是以服务端数据为准、本地未同步的操作要保留。具体实现是给每个购物车操作加一个本地自增的opId,同步时带上最后操作的opId,服务端返回增量更新。
订单状态机是整个商城业务里逻辑最严谨的地方。我把订单状态定义为有限状态机,所有状态转移都集中在reducer里处理,不允许业务组件直接修改状态字段:
typescript复制type OrderStatus =
| 'pending_payment' // 待付款
| 'paid' // 已付款待发货
| 'shipped' // 已发货
| 'completed' // 已完成
| 'cancelled' // 已取消
| 'refunding' // 退款中
| 'refunded'; // 已退款
订单列表里有大量倒计时需求:待付款订单的“剩余xx分xx秒取消”,拼团订单的“剩余xx小时结束”。倒计时逻辑不能只在前端做,因为App切后台、手机锁屏都会导致JS线程暂停或定时器不准。我们的方案是:每次接口返回订单数据时带上服务端时间serverTime,前端倒计时基于serverTime + 本地耗时差计算。这样即使定时器因为后台挂起出现了延迟,重新唤醒时也能通过校准时间恢复正确显示。
这里有个坑值得提醒:用Date.now()直接计算剩余时间,在用户手动改了手机系统时间之后会导致倒计时混乱。解决方案是用性能单调时钟performance.now()计算耗时差,再与服务器时间相加。这样手机改时间也不会影响倒计时准确性。
2.4 推荐系统与商家模块的客户端实现
推荐模块在“猜你喜欢”场景下常见的是feed流混排。服务端负责算法排序,客户端要处理的是无限滚动的渲染策略。我们用FlatList分批渲染,每批次20个商品卡片。为了滑动看起来更“原生”,卡片高度不是固定的,而是根据图片宽高比动态计算。这里有个关键优化:动态高度的列表在滚动时容易出现白屏跳动,处理办法是getItemLayout没法用的情况下,给每个list item外面包一个固定最小高度的容器,图片加载完成后再调整高度,配合onLayout回调更新缓存高度。
推荐feed流的另一大痛点是列表项类型多样化。首屏有banner位、金刚区icon、活动入口,往下是商品卡片、活动推广位、店铺入口混排。这种异构列表用FlatList的renderItem里做switch判断即可,但需要注意每个item必须返回一个稳定的React元素,不要在renderItem内部创建匿名组件。
商家模块更像是“商城里的独立小程序”。商家主页包括店招、公告、分类导航、商品tab、优惠券领取等。在RN里实现这个模块,我的思路是把商家主页设计成服务端下发配置、客户端动态渲染的模式。服务端返回一组“模块描述文件”,包含组件类型、排序、数据源ID,客户端用一个DynamicRenderer组件统一渲染。这样商家改版店铺装修时不需要发版App,只需要改服务端配置即可。这本质上就是一套简化版的动态化方案,不依赖任何DSL框架,降级和回滚都非常方便。
3. 鸿蒙跨端适配的完整实践
3.1 鸿蒙适配的现状与路线选择
鸿蒙适配这件事,首先要搞清楚一个背景:鸿蒙NEXT从系统层面不再兼容Android APK,这意味着原来RN跑在Android上、包一层鸿蒙兼容框架的做法行不通了,需要真正的原生化适配。
目前社区的适配路线主要有两条:一条是使用社区的React Native for OpenHarmony(RNOhoOS/RTN)方案,将RN运行时桥接到鸿蒙的ArkTS运行时上;另一条是用Taro这类编译型跨端框架,把React代码编译为鸿蒙原生ArkUI组件。两条路线各有优劣,我实际验证下来,如果存量RN项目比较大,迁移RTN路线成本更低,因为业务JS代码几乎不用动;如果是全新项目且团队前端能力更强,Taro直接生成鸿蒙原生页面性能更好。
我们项目选择的是基于RN 0.72分支的鸿蒙适配。选择这个分支的主要原因是社区适配相对成熟,ArkTS的接口封装得比较完整,同时我们的业务代码主要依赖的第三方库(react-navigation、axios、fast-image等)在0.72上都有对应的鸿蒙实现或可以降级替代。
这里要给一个明确建议:如果你的RN版本低于0.68,鸿蒙适配成本会大幅上升,建议先做RN核心版本升级,再启动鸿蒙适配。版本跳跃太大会带来大量第三方库的API兼容问题,适配鸿蒙时排查起来非常痛苦。
3.2 鸿蒙适配的完整接入流程
下面是我们实际走通的鸿蒙接入流程,每一步都标注了容易出问题的点。
第一步是环境准备。需要安装DevEco Studio,创建鸿蒙工程的时候选择empty ability模板。重点在于配置好SDK路径和签名证书。鸿蒙应用调试必须签名,没有签名无法安装到真机或模拟器。开发调试阶段用auto签名即可,不需要申请正式证书,但发布时必须用企业或个人证书,这个周期要预留出来,别拖到上线前才想起来。
第二步是创建RN桥接工程。在鸿蒙工程里添加HarmonyOS依赖,然后在Entry模块里配置oh-package.json5,引入RN运行时依赖:
json复制{
"dependencies": {
"react-native-harmony": "0.72.x"
}
}
这一步很容易踩坑:必须保证鸿蒙侧的RN版本与JS侧react-native版本匹配,否则运行时会因为Gradle依赖tree不一致导致初始化失败。如果版本对不上,最典型的症状就是App启动后白屏,日志里出现so库加载失败错误。
第三步是生成JS侧的Bundle包。鸿蒙运行时加载的是JS Bundle产物,和Android、iOS的逻辑一样。我们通过在工程里配置bundle命令,把入口文件打成harmony.jsbundle。注意这个bundle包要用Harmony平台的platform参数来构建,因为它们和Android的bundle在个别内置模块的解析上存在差异。
第四步是桥接层实现。这是最核心的工作。我们原有的bridge层基于Android/iOS实现,鸿蒙侧需要新建一套实现。以登录模块为例,鸿蒙端的账号体系与Android端不同,不能直接用微信OpenSDK,而是调用鸿蒙的Account Kit能力。我在bridge层底下新增了一个auth-harmony.ts,实现同样的接口,内部调用鸿蒙SDK。
整个接入流程如果用表格梳理的话是这样的:
| 步骤 | 核心工作 | 常见坑 |
|---|---|---|
| 环境准备 | DevEco Studio安装、SDK配置、签名 | 签名缺失导致无法安装/运行 |
| RN桥接工程集成 | oh-package配置RN依赖 | RN版本与Harmony版本不匹配 |
| JS Bundle构建 | Harmony平台打包命令 | 平台参数不对导致运行报错 |
| 原生桥接适配 | 登录、支付、推送模块重写 | 系统API命名差异导致编译失败 |
| 组件兼容替换 | 替换不支持的第三方原生组件 | 原生组件缺失导致白屏或崩溃 |
| 调试联调 | hdc工具真机调试 | 日志不完整,需打开RN调试模式 |
3.3 鸿蒙侧核心桥接代码示例
以登录模块为例,看一下鸿蒙侧的桥接实现长什么样。这里用的是ArkTS的格式,和Android的Java/Kotlin接口不一样,需要重新适配。
typescript复制// harmony/entry/src/main/ets/AuthBridge.ets
import { account } from '@kit.BasicServicesKit';
export class AuthBridge {
loginByHarmony(): Promise<string> {
return new Promise((resolve, reject) => {
const authParam = new account.AuthParams();
authParam.scopes = ['email', 'profile'];
const auth = account.getDefaultAccountManager();
auth.login(authParam, (err, result) => {
if (err) {
reject(err);
return;
}
const token = result.getAuthToken();
resolve(token);
});
});
}
}
这段代码调用的是鸿蒙的Account Kit能力,获取用户的授权Token。拿到Token之后,业务侧会拿它去我们自己的服务端换取会话凭证,后续所有接口都走统一的鉴权流程。这样鸿蒙端和Android/iOS端在业务侧完全没有差异,真正做到了“一套业务逻辑、多点原生接入”。
支付模块的适配是同样的逻辑。鸿蒙NEXT的应用内支付走的是IAP(In-App Purchases)能力,和Android的Pay、iOS的IAP都不同。我们在适配时把支付桥接抽象成三个阶段:创建订单、拉起支付、支付结果回调。每个阶段在鸿蒙侧都有对应的API调用,结果统一返回给JS层。这里有个细节:鸿蒙的支付Result回调不能像Android的onActivityResult那样拿到页面resultCode,必须通过订阅支付结果事件来监听,适配时要特别注意回调链路的差异。
推送模块相对简单,鸿蒙侧有自己的Push Kit,需要配置通知权限,代码上类似Android的缺省channel配置。但因为每家厂商的推送服务接入方式不同,这块的桥接适配最花时间,建议在项目里把推送封装出一个单独的provider接口,后续对接各厂商推送时只新增实现即可。
3.4 鸿蒙适配中踩过的高频坑
适配过程中我们遇到了不少问题,挑几个对后续做这个方向的团队价值最大的记录一下。
第一个坑是启动白屏。鸿蒙端上RN应用最典型的问题就是白屏。排查思路和Android类似:第一步看JS Bundle有没有加载成功,第二步看原生so库有没有加载失败,第三步看JS侧有没有运行时报错。我们遇到过一次比较隐蔽的情况:鸿蒙端加载Bundle时用的是嵌入式文件路径,但代码里写成了远程Bundle地址,导致运行时在Release包上拉不到JS文件直接白屏。定位方法是用hdc拉取App的log,搜索关键字ReactNativeJS,所有JS侧日志都会打在这个tag下面。
第二个坑是部分第三方组件的兼容性问题。比如react-native-fast-image在鸿蒙上没有对应的原生实现,直接使用会导致崩溃。面对这类问题,我们的策略是:优先寻找鸿蒙替代方案,找不到就降级为普通Image组件或自研一个简易的图片缓存组件。不需要一开始就追求所有组件完全一致,先保证功能可用,再逐步补齐性能优化。
第三个坑是网络请求的底层差异。RN在Android上走的OkHttp,在鸿蒙上底层实现不一样。我们遇到过一次因为服务端证书校验策略不同,导致鸿蒙端HTTPS请求全部失败的问题。排查方式是对比Android和鸿蒙端的网络日志,最后确认是鸿蒙默认的网络安全配置和Android不一致。适配时把鸿蒙工程的网络安全配置改为和Android等价即可。
第四个坑是时间同步。鸿蒙设备如果开了自动时间,默认和Android一样走NTP同步,但部分设备在弱网环境下会返回一个明显异常的时间戳,导致订单倒计时出现负数。处理方案很粗暴但很有效:所有时间字段以服务端返回为准,客户端不做任何“聪明”的时间修正,发现异常时间差(超过5分钟)就直接拉最新服务端时间。
4. 性能优化与典型问题排查
4.1 启动白屏的专项优化
从热搜榜上就能看出来,“react native 启动白屏”是大量RN开发者的共同痛点。这个问题的本质是:JS线程启动、Bundle加载、首屏渲染这几个环节都发生在“看到内容”之前,任何一个环节慢,用户感知就是白屏。
在商城这个场景里,白屏对转化率的影响非常直接。我们的优化组合拳是这样的:
第一,压缩Bundle体积。把业务代码按页面维度做拆包,首屏只加载首页需要的模块,其他模块在进入对应页面时再动态加载。举个例子,支付页、订单详情页这些用户不会一进来就打开的模块,全部不走主Bundle。这一步能把主Bundle体积压缩30%到40%。
第二,启动阶段的并行初始化。RN的初始化链路里,很多任务其实没有依赖关系。比如Bridge初始化、数据预加载、首屏渲染的数据请求,这些完全可以并行。我们在App启动时提前预加载首页接口数据,让首屏渲染完成后数据直接可用,不需要再走loading状态。用户在冷启动后几乎能立刻看到商品列表,体验差距非常大。
第三,首屏渲染优化。首页首屏部分不渲染全量组件,只渲染视口内的内容。图片全部走渐入加载,不要一次性请求所有首屏图片资源。实测下来,这三步优化合计能把从点击App图标到首页首屏完全可交互的时间从3.5秒左右压缩到1.8秒左右。对用户体感来说,就是从“怎么还没打开”变成了“好像秒开了”。
4.2 长列表与内存优化
第二个优化重点是长列表的内存占用。商品feed流列表无限加载,如果每个item的图片都不释放,内存迟早爆掉。
先说FlatList的三个关键优化参数。getItemLayout能让列表跳过动态测量直接定位,但要求item高度固定;商城feed流高度不固定,所以我更常用windowSize和maxToRenderPerBatch来控制渲染批量。这两个参数的核心逻辑是让列表始终保持较少的渲染节点,但滚动时要保证提前渲染下一屏的少量内容,避免白屏。实测下来windowSize设为7、maxToRenderPerBatch设为8,滚动流畅度和内存占用比较均衡。
再看图片缓存。fast-image在Native层有单独的图片缓存池,不在JS内存里,这是它占用低的根本原因。在鸿蒙端没有fast-image的情况下,我们需要自建一个简易图片缓存:用一个全局Map做内存缓存,LRU策略淘汰;磁盘缓存直接复用RN自身图片加载器的能力,只做URL到本地文件路径的映射管理。
最后说离屏渲染。商家主页和商品详情页这种复杂的动态模块,页面高度可能超出视口很多,用户滑动时如果每帧都重新计算布局,画面很容易卡顿。我的方案是把description区域用collapsable={false}包一层,同时把商家的店招图片单独放在一个固定高度的容器里,避免页面滚动时反复触发布局计算。这个细节看起来不起眼,但在低端机上差异特别明显。
4.3 高频问题排查速查表
把我们在整个项目中遇到的高频问题整理成一张速查表,方便遇到问题直接对照:
| 问题 | 典型表现 | 排查思路与解决方式 |
|---|---|---|
| 启动白屏 | 点击图标后长时间空白 | 检查JS Bundle是否加载;查看ReactNativeJS日志;检查so库加载状态 |
| 列表滚动卡顿 | 滑动帧率崩,有白屏残留 | FlatList设置windowSize/maxToRenderPerBatch;优化图片缓存 |
| 倒计时不准 | 切后台再回来时间不对 | 用服务端时间+performance.now()计算,不依赖Date.now() |
| 循环滚轮不居中 | 滚轮选择项不在中线位置 | 监听onMomentumScrollEnd后计算偏移量并做snapToInterval处理 |
| 鸿蒙端图片加载崩溃 | 使用fast-image崩溃 | 鸿蒙端替换或降级为普通RN Image组件 |
| 鸿蒙端支付回调丢失 | 支付完成无回调页面状态异常 | 改为订阅支付结果事件,而不是依赖页面生命周期回调 |
| 购物车状态不同步 | 列表和结算页数据不一致 | 统一走redux action修改购物车,禁止组件直接setState |
排查问题有一条通用的铁律:先从日志入手,不要猜测。RN项目在Android和鸿蒙上可以通过adb logcat或hdc拉日志,iOS用Xcode的console。JS侧的错误都会打印在ReactNativeJS标签下,原生侧的错误看so库加载和系统自带标签。先确认是JS层还是原生层,再往下定位,效率会高很多。
最后再分享一个我们在工程化层面的体会:整个鸿蒙适配项目最后统计下来,真正花在桥接层改写上的时间只有总量的三分之一,剩下三分之二都花在环境配置、版本对齐、白屏排查这些“环境类”问题上。所以如果团队准备做鸿蒙适配,强烈建议先花一两天时间把开发环境、签名流程、日志输出链路完全跑通,再开始动业务代码。环境的坑越早趟平,后面就越顺畅。这个经验不是从文档里看来的,是我们真金白银踩出来的。
