React中使用RTK Query实现带加载进度条的文件上传功能咨询
基于RTK Query实现文件上传组件的实现方案
1. 定义支持进度监听的RTK Query上传端点
RTK Query的fetchBaseQuery原生支持上传进度回调配置,不需要额外引入第三方请求库,核心配置如下:
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react'; export const uploadApi = createApi({ reducerPath: 'uploadApi', baseQuery: fetchBaseQuery({ baseUrl: '/api' }), endpoints: (builder) => ({ uploadFile: builder.mutation({ query: (formData) => ({ url: '/file/upload', method: 'POST', body: formData, // 开启上传进度监听 fetchOptions: { onUploadProgress: (progressEvent) => { const percentCompleted = Math.round( (progressEvent.loaded * 100) / progressEvent.total ); // 执行透传的进度更新回调 formData.get('onProgress')?.(percentCompleted); } } }), // 统一预处理返回错误 transformErrorResponse: (response) => { const errMap = { 400: '文件格式不合法,请重新上传', 413: '文件大小超出服务端限制', 500: '服务端异常,请稍后重试' } return { status: response.status, message: errMap[response.status] || response.data?.message || '上传失败' } } }) }) }); export const { useUploadFileMutation } = uploadApi;
注意:FormData中传入的进度回调函数不会被序列化提交到服务端,不会干扰正常请求参数。
2. 拖拽上传功能实现
直接用原生拖拽事件实现,不需要引入额外依赖,核心逻辑如下:
import { useState } from 'react'; import { useUploadFileMutation } from './uploadApi'; const FileUpload = () => { const [isDragOver, setIsDragOver] = useState(false); const [uploadProgress, setUploadProgress] = useState(0); const [uploadError, setUploadError] = useState(null); const [uploadFile, { isLoading }] = useUploadFileMutation(); // 阻止默认拖拽行为,避免浏览器直接打开文件 const handleDragOver = (e) => { e.preventDefault(); setIsDragOver(true); }; const handleDragLeave = (e) => { e.preventDefault(); setIsDragOver(false); }; const handleDrop = (e) => { e.preventDefault(); setIsDragOver(false); const file = e.dataTransfer.files[0]; validateAndUpload(file); }; // 统一文件校验和上传逻辑,普通文件选择也复用该方法 const validateAndUpload = (file) => { if (!file) return; // 前置格式校验 const allowedTypes = ['.csv', '.xlsx', '.xls']; const fileExt = file.name.slice(file.name.lastIndexOf('.')).toLowerCase(); if (!allowedTypes.includes(fileExt)) { setUploadError('仅支持上传CSV、XLS、XLSX格式的文件'); return; } // 前置大小校验,可根据需求调整上限 if (file.size > 10 * 1024 * 1024) { setUploadError('文件大小不能超过10M'); return; } setUploadError(null); setUploadProgress(0); const formData = new FormData(); formData.append('file', file); formData.append('onProgress', setUploadProgress); // 触发上传 uploadFile(formData) .unwrap() .then(() => { setUploadProgress(100); // 这里补充上传成功后的业务逻辑 }) .catch((err) => { setUploadError(err.message); setUploadProgress(0); }); }; // 取消上传功能 const handleCancel = () => { uploadFile.abort(); setUploadProgress(0); setUploadError(null); };
拖放区域UI代码参考:
return ( <div className="w-full max-w-2xl mx-auto p-4"> {/* 拖拽区域 */} <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} className={`border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors ${ isDragOver ? 'border-blue-500 bg-blue-50' : 'border-gray-300' }`} > <p>拖拽文件到此处上传,或点击下方按钮选择文件</p> <input type="file" accept=".csv,.xlsx,.xls" onChange={(e) => validateAndUpload(e.target.files[0])} className="hidden" id="file-upload" /> <label htmlFor="file-upload" className="text-blue-500 underline cursor-pointer mt-2 inline-block"> 选择文件 </label> </div> {/* 进度条 */} {isLoading && ( <div className="mt-4"> <div className="w-full h-2 bg-gray-200 rounded-full overflow-hidden"> <div className="h-full bg-blue-500 transition-all duration-300 ease-in-out" style={{ width: `${uploadProgress}%` }} /> </div> <div className="flex justify-between items-center mt-1"> <span className="text-xs text-gray-600">{uploadProgress}%</span> <button onClick={handleCancel} className="text-xs text-red-500">取消上传</button> </div> </div> )} {/* 错误提示 */} {uploadError && ( <p className="mt-2 text-sm text-red-500">{uploadError}</p> )} </div> ) } export default FileUpload;
3. 补充优化点
- 多文件上传:修改校验逻辑遍历
FileList数组,给每个文件生成独立的进度状态即可 - 大文件分片上传:可以结合
file.slice方法拆分文件,RTK Query批量发送分片请求,服务端拼接即可 - 上传成功状态持久化:可以把上传成功的文件信息存在RTK的cache里,避免重复上传
内容的提问来源于stack exchange,提问作者slycooper14
相关产品推荐
相关产品推荐

