如何解决Redux Toolkit中Promise原生resolve/reject函数序列化失败问题?
解决方案:避免将Promise回调存入Redux
核心问题在于Redux要求状态必须可序列化,而原生Promise的resolve/reject函数无法通过toString()序列化恢复。正确的思路是把回调函数留在组件钩子内部,用唯一ID关联Redux中的弹窗状态,而非尝试序列化函数。
步骤1:调整Redux状态结构
只存储弹窗的配置信息和唯一ID,不涉及任何函数:
// alertSlice.ts import { createSlice, PayloadAction } from '@reduxjs/toolkit'; export interface AlertState { id?: string; title?: string; confirmLabel: string; cancelLabel: string; } const initialState: AlertState = { confirmLabel: '确认', cancelLabel: '取消' }; const alertSlice = createSlice({ name: 'alert', initialState, reducers: { setAlert: (state, action: PayloadAction<Omit<AlertState, 'id'> & { id: string }>) => { return { ...action.payload }; }, closeAlert: () => initialState, // 仅用于触发回调的标记action,无需修改状态 confirmAlert: (_, action: PayloadAction<string>) => {}, cancelAlert: (_, action: PayloadAction<string>) => {} } }); export const { setAlert, closeAlert, confirmAlert, cancelAlert } = alertSlice.actions; export const selectAlert = (state: { alert: AlertState }) => state.alert; export default alertSlice.reducer;
步骤2:重写useConfirm钩子
用useRef维护一个ID到Promise回调的映射表,通过监听Redux的action来触发对应的resolve/reject:
// useConfirm.ts import { useAppDispatch, useAppSelector } from './store/hooks'; import { setAlert, closeAlert, confirmAlert, cancelAlert, selectAlert } from './alertSlice'; import { useRef, useEffect } from 'react'; import { v4 as uuidv4 } from 'uuid'; // 可替换为自定义ID生成逻辑 export const useConfirm = () => { const dispatch = useAppDispatch(); const alert = useAppSelector(selectAlert); // 存储待处理的弹窗回调:key=弹窗ID,value=resolve/reject函数 const pendingCallbacks = useRef<Record<string, { resolve: () => void; reject: () => void }>>({}); // 监听确认/取消的action,执行对应回调 useEffect(() => { const handleAction = (action: any) => { if (confirmAlert.match(action)) { const callback = pendingCallbacks.current[action.payload]; callback?.resolve(); delete pendingCallbacks.current[action.payload]; dispatch(closeAlert()); } else if (cancelAlert.match(action)) { const callback = pendingCallbacks.current[action.payload]; callback?.reject(); delete pendingCallbacks.current[action.payload]; dispatch(closeAlert()); } }; const unsubscribe = dispatch.subscribe(handleAction); return unsubscribe; }, [dispatch]); const Confirm = async (alertOptions: Omit<AlertState, 'id'>) => { return new Promise<boolean>((resolve, reject) => { const alertId = uuidv4(); // 存储回调函数,不传入Redux pendingCallbacks.current[alertId] = { resolve: () => resolve(true), reject: () => resolve(false) // 按需求,取消时返回false }; // 仅将弹窗配置和ID传入Redux dispatch(setAlert({ ...alertOptions, id: alertId })); }); }; return Confirm; };
步骤3:修改弹窗组件
弹窗组件只需根据Redux状态渲染,并在按钮点击时触发对应的action:
// AlertComponent.tsx import { useAppDispatch, useAppSelector } from './store/hooks'; import { selectAlert, confirmAlert, cancelAlert } from './alertSlice'; import Button from './Button'; export const AlertComponent = () => { const alert = useAppSelector(selectAlert); const dispatch = useAppDispatch(); if (!alert.id) return null; return ( <div className="alert-overlay"> <div className="alert-content"> {alert.title && <h3>{alert.title}</h3>} <div className="alert-buttons"> <Button onClick={() => dispatch(cancelAlert(alert.id))} buttonStyle="ghost" > {alert.cancelLabel} </Button> <Button onClick={() => dispatch(confirmAlert(alert.id))} > {alert.confirmLabel} </Button> </div> </div> </div> ); };
方案优势
- 完全符合Redux状态可序列化的要求,无需尝试序列化原生函数
- 回调函数留在钩子内部,避免了序列化/反序列化的问题
- 通过唯一ID关联弹窗和回调,逻辑清晰且易于维护
内容的提问来源于stack exchange,提问作者Demian
相关产品推荐
相关产品推荐

