Redux Toolkit不可序列化值问题解决(不关闭校验方案)
解决Redux中存储File/Blob导致的序列化校验错误问题
问题背景
在Redux store的action payload中存储了File类型的下载文件,在saga中完成校验后,通过以下代码构建FormData发送至后端:
const form = new FormData(); if (privateKey && privateKey instanceof Blob) { const blob = new Blob([privateKey], { type: 'multipart/form-data' }); form.append('privateKey', blob);
每次下载文件后控制台触发序列化校验错误,需要在不关闭序列化校验的前提下解决该问题。
核心原因
Redux要求action payload和store中的state必须是可序列化的数据(如字符串、数字、数组、普通对象),而File/Blob属于浏览器原生的不可序列化对象,将其放入action payload时,会被Redux的serializableCheck中间件检测到并抛出错误。
可行解决方案
1. 避免在Redux中存储File/Blob实例,改用元数据+临时存储
- 只在Redux中存储文件的可序列化元数据(如文件名、大小、MIME类型),实际的File/Blob对象存储在组件的
useRef、全局变量或IndexedDB中。 - 在saga处理时,直接从临时存储中获取文件对象,无需从Redux state/payload中读取。
- 示例代码:
// 组件中 const fileRef = useRef(null); const handleFileSelect = (file) => { fileRef.current = file; // 只dispatch元数据到Redux dispatch(setFileMeta({ name: file.name, size: file.size, type: file.type })); }; // saga中 function* uploadFileSaga() { const file = fileRef.current; if (file) { const form = new FormData(); form.append('privateKey', file, file.name); // 发送请求 yield call(api.upload, form); } }
2. 优化FormData构建逻辑,移除不必要的Blob转换
File本身是Blob的子类,无需重新创建Blob实例,直接将原File对象append到FormData即可,同时建议加上文件名参数,方便后端处理。- 修正后的代码:
const form = new FormData(); if (privateKey && privateKey instanceof Blob) { // 直接使用原对象,无需重新包装 form.append('privateKey', privateKey, privateKey.name || 'private.key'); }
3. 使用Redux Toolkit的异步工具处理非序列化数据
- 若需要通过Redux流程传递文件,可使用Redux Toolkit的
createAsyncThunk,它允许在payload中传递非序列化数据,但需注意不要将File/Blob存入store的state,仅在异步流程的payload中临时传递。 - 示例代码:
import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'; export const uploadFile = createAsyncThunk( 'file/upload', async (file, { rejectWithValue }) => { try { const form = new FormData(); form.append('privateKey', file, file.name); const response = await fetch('/api/upload', { method: 'POST', body: form }); return await response.json(); } catch (error) { return rejectWithValue(error.message); } } ); // slice中只存储上传状态,不存储文件对象 const fileSlice = createSlice({ name: 'file', initialState: { loading: false, error: null, uploadResult: null }, extraReducers: (builder) => { builder .addCase(uploadFile.pending, (state) => { state.loading = true; state.error = null; }) .addCase(uploadFile.fulfilled, (state, action) => { state.loading = false; state.uploadResult = action.payload; }) .addCase(uploadFile.rejected, (state, action) => { state.loading = false; state.error = action.payload; }); } }); export default fileSlice.reducer;
内容的提问来源于stack exchange,提问作者Irina Mysko
相关产品推荐
相关产品推荐

