Redux Toolkit 向payload传Error报不可序列化错误(不禁用serializableCheck)解决
所有方案均满足Redux Toolkit默认的可序列化校验规则,不会触发Non-serializable value was detected in action报错:
提取Error核心字段构造纯普通对象,作为action payload传入
直接传入Error实例被拦截的核心原因是,Error是带原型链的非普通对象,不属于RTK序列化校验允许的原始值、Plain Object、数组类型。你可以在dispatch前,手动提取Error实例上的可序列化字段,构造纯对象再传入:// 会触发校验报错的写法 dispatch(handleRequestError(new Error('接口超时'))) // 合规写法 const rawError = new Error('接口超时') dispatch(handleRequestError({ name: rawError.name, message: rawError.message, stack: rawError.stack, // 可按需挂载自定义错误码、错误元信息 code: rawError.code }))存入store的字段全部是字符串、数字这类可序列化值,可直接通过
useSelector取出传递给组件;如果需要恢复完整Error实例能力,在组件拿到plain错误对象后,通过const restoredError = new Error(errObj.message)重新构造实例,再把其他自定义字段挂在实例上即可。依托
createAsyncThunk的内置错误处理机制,自动生成合规的可序列化错误payload
RTK的createAsyncThunk本身就做了错误信息的序列化适配,不需要手动处理Error实例透传的问题:在异步流程捕获到错误时,通过rejectWithValue返回纯普通对象格式的错误信息,这个返回值会直接作为rejected action的payload,全程不会触发序列化校验:const fetchOrderDetail = createAsyncThunk( 'order/fetchDetail', async (orderId, { rejectWithValue }) => { try { const res = await fetch(`/api/order/${orderId}`) if (!res.ok) throw new Error(`请求失败,状态码${res.status}`) return res.json() } catch (err) { // 返回的纯对象会作为action payload,完全通过序列化校验 return rejectWithValue({ message: err.message, statusCode: res.status }) } } )在slice的reducer中匹配
rejected状态,把拿到的错误信息存入state,组件即可直接从state读取错误信息,不需要手动构造带Error的action。旁路映射表存储原始Error实例,store中仅传递可序列化的唯一标识
如果业务流程中确实需要在组件中使用原始Error实例的全量能力(比如依赖instanceof判断错误类型、调用Error实例的内置方法),可以不用把Error本身放进store:- 在模块作用域下维护一个Map结构,作为Error实例的存储映射,这个Map不属于store的一部分,不会触发任何序列化检查
- 给每个捕获到的Error分配一个唯一字符串ID,把<ID, Error实例>的键值对存在这个Map里
- dispatch action时,只把这个唯一ID作为payload传入store
// 模块级旁路存储,不进Redux store const errorStore = new Map<string, Error>() let incrementId = 0 const registerError = (err: Error) => { const errId = `err_${incrementId++}` errorStore.set(errId, err) return errId } // 组件中捕获错误后的调用逻辑 const caughtErr = new Error('自定义业务错误') const errId = registerError(caughtErr) dispatch(setActiveError({ errId })) // 组件读取错误时的逻辑 const activeErrId = useSelector(state => state.app.activeErrId) const activeError = errorStore.get(activeErrId)整个流程中进入action、存入store的只有字符串类型的errId,完全符合序列化校验要求,同时能在组件中拿到原始Error实例的全部能力。注意:读取完对应的Error实例后,可通过
errorStore.delete(errId)清理映射表中的引用,避免不必要的内存占用。
内容的提问来源于stack exchange,提问作者Pavel

