React+Redux Async Thunk下载文件跳转404问题求助
问题描述
我有一个React Redux应用和Django后端,后端接口从S3获取文件并转为Base64编码返回字符串。希望Redux异步请求完成后,无需跳转新页面即可下载文件,但当前用window.location.href触发下载时会跳转到404页面。
前端代码
DocumentReducer.tsx(含必要导入)
export const getDocument = createAsyncThunk("document/download", async (payload: any) => { const {id} = payload const response = await instance.get(`tables/document/download/${id}`) return response.data }) const documentSlice = createSlice({ name: "document", initialState: initialState, reducers:{}, extraReducers: (builder) => { ... builder.addCase(getDocument.fulfilled, (state, action) => { const file = action.payload.file window.location.href = 'data:application/octet-stream;base64,' + file state.status = 'fulfilled' }) } })
ExportComponent.tsx(含必要导入)
export const ExportButton: React.FC<ExportButtonProps> = ({document_id}) => { const dispatch = useAppDispatch() const handleClick = () => { const payload = {id: document_id} dispatch(getDocument(payload)) } return ( <Button variant="contained" size="small" onClick={handleClick}> <div className="flex gap-3"> <p>Export</p> <ArrowDownTrayIcon className='w-4 text-white'/> </div> </Button> ) }
后端代码(views.py)
def encode_document(s3_key): """ get document from s3 and base 64 encode the document """ buffer = BytesIO() output_stream = BytesIO() S3_CLIENT.download_fileobj("BUCKET_NAME", s3_key, buffer) encoded = b64encode(buffer.getvalue()).decode() return encoded class DocumentS3(APIView): parser_classes = [JSONParser] permission_classes = [IsAuthenticated] def get(self, request, document_id): document = Document.objects.get(id=document_id) encoded = encode_document(document.s3_key) print(encoded[:10]) return Response({"file": encoded}, status=status.HTTP_200_OK)
后端已成功返回编码字节,但下载链接跳转到404,求Redux中实现正确下载的方法。
解决方案
window.location.href直接设置data URL的方式易被浏览器拦截或解析异常,导致跳转404。正确做法是创建隐藏<a>标签模拟点击触发下载,完成后销毁标签,完全不会跳转页面。
修改Redux回调逻辑
更新DocumentReducer.tsx中getDocument.fulfilled的处理代码:
builder.addCase(getDocument.fulfilled, (state, action) => { const file = action.payload.file; // 解码Base64字符串为Blob对象 const byteCharacters = atob(file); const byteNumbers = new Array(byteCharacters.length); for (let i = 0; i < byteCharacters.length; i++) { byteNumbers[i] = byteCharacters.charCodeAt(i); } const byteArray = new Uint8Array(byteNumbers); const blob = new Blob([byteArray], { type: 'application/octet-stream' }); // 创建临时下载链接 const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; // 自定义文件名,可根据后端返回或业务需求调整 a.download = `document-${action.meta.arg.id}.pdf`; document.body.appendChild(a); a.click(); // 清理临时资源,避免内存泄漏 document.body.removeChild(a); URL.revokeObjectURL(url); state.status = 'fulfilled'; })
优化说明
- 将Base64转Blob比直接用data URL更稳定,尤其适配大文件场景
- 通过临时
<a>标签触发下载,彻底避免页面跳转问题 - 记得调用
URL.revokeObjectURL释放内存 - 若知道文件具体MIME类型(如PDF用
application/pdf),替换application/octet-stream可提升浏览器文件识别度
可选优化:后端直接返回文件流
如果文件体积较大,Base64会增加33%的传输体积,推荐后端直接返回文件流:
- 后端修改返回逻辑:
def get(self, request, document_id): document = Document.objects.get(id=document_id) buffer = BytesIO() S3_CLIENT.download_fileobj("BUCKET_NAME", document.s3_key, buffer) buffer.seek(0) response = HttpResponse(buffer, content_type='application/octet-stream') response['Content-Disposition'] = f'attachment; filename="document-{document_id}.pdf"' return response
- 前端修改请求配置:
export const getDocument = createAsyncThunk("document/download", async (payload: any) => { const {id} = payload const response = await instance.get(`tables/document/download/${id}`, { responseType: 'blob' // 指定响应类型为Blob }) return { blob: response.data, id } })
- Redux回调简化:
builder.addCase(getDocument.fulfilled, (state, action) => { const { blob, id } = action.payload; const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `document-${id}.pdf`; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); state.status = 'fulfilled'; })
内容的提问来源于stack exchange,提问作者Rob Campbell
相关产品推荐
相关产品推荐

