React Native 在鸿蒙上跑起来之后,第一件事不是写业务,而是把错误处理想清楚。我之前那篇聊过 RN 鸿蒙版的环境搭建和启动流程,今天这篇专门说说 Redux 中间件层怎么做错误处理,以及在这过程中踩到的一些鸿蒙特有的坑。
先说结论:在 React Native 鸿蒙版项目里,Redux 中间件不只是分发 action 的管道,更是整个 App 错误信息的汇聚点和恢复策略的执行层。把错误处理收敛到中间件里做,比在每个页面 try-catch 要干净得多,也比在 reducer 里处理副作用要安全得多。
1. 为什么需要在中间件层做错误处理
1.1 React Native 鸿蒙版的特殊背景
React Native 鸿蒙版跑的是 OpenHarmony 的 JS/Native 混合环境,它的异常传播链路和 iOS/Android 上不太一样。客户端代码经过 ArkTS 运行时和 RN 的 C++ 层交互,中间隔着一层桥接,出了问题往往不只是 JS 层的报错,还可能伴随 native 层的异常状态。
在这样的环境下,错误处理如果散落在各个业务页面里,会出现几个问题:
- 错误信息碎片化,难以统一聚合上报
- UI 层和错误恢复逻辑耦合,页面代码变得臃肿
- 遇到 native 层的异常时,JS 层拿到的错误信息已经丢失了上下文
把错误处理放到 Redux 中间件层,相当于给所有 action 加了一个统一的“安检口”,既能拦截异常,又能在这个位置做聚合并触发恢复逻辑。
1.2 中间件方案的选型考量
Redux 中间件里做错误处理,主流方案有三个:自己写一个 middleware、用 redux-logger 扩展、或者集成 redux-saga 的错误通道。我选了自研中间件的方式,原因很简单——鸿蒙版的 RN 生态还比较新,第三方中间件对鸿蒙的兼容性参差不齐,而且我们需要针对鸿蒙特有的场景做定制处理,自研可控性最强。
自研中间件最核心的价值在于:它可以把“错误捕获、错误分类、状态重置、用户通知”整合成一条流水线,而且这条流水线对业务代码完全透明。业务侧只需要像平常一样 dispatch action,完全不用关心错误逻辑。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 中间件错误处理的核心设计
2.1 整体架构:三层拦截模型
我设计的中间件错误处理架构分成三层:捕获层、分类层、恢复层。
捕获层的职责是拦截 action 流里的异常。dispatch 一个 action 后,如果 reducer 执行时抛错,或者异步 action 里有未捕获的 rejection,都会在这里被截获。
分类层根据错误特征决定处理策略。比如网络超时、渲染异常、设备能力缺失,这三类错误后续的处理方式完全不同。网络超时可能需要提示用户重试,渲染异常可能需要重置整个页面状态,设备能力缺失则需要引导用户升级版本或者走兼容逻辑。
恢复层执行具体的恢复动作,比如 dispatch 一个 reset action、跳转错误页、弹 toast 提示,并同步把错误信息上报到归集服务。
2.2 中间件的代码骨架
javascript复制import { Middleware, MiddlewareAPI, Dispatch, AnyAction } from 'redux';
import { ErrorClassifier, ErrorRecoveryStrategy } from './errorTools';
interface ErrorHandlingOptions {
onError?: (error: Error, action: AnyAction) => void;
recoveryStrategies?: Record<string, ErrorRecoveryStrategy>;
enableLogging?: boolean;
}
export function createErrorHandlingMiddleware(options: ErrorHandlingOptions = {}) {
const { onError, recoveryStrategies = {}, enableLogging = true } = options;
const classifier = new ErrorClassifier();
return (store: MiddlewareAPI) => (next: Dispatch) => (action: AnyAction) => {
try {
const result = next(action);
if (result instanceof Promise) {
return result.catch((error) => {
handleError(store, error, action, classifier, recoveryStrategies, onError, enableLogging);
return Promise.reject(error);
});
}
