You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ReactJS如何实现PUT请求文件上传跨页面/刷新不中断

React SPA大文件上传跨路由/刷新续传实现方案

你的初始判断完全正确:浏览器手动刷新会销毁当前页面所有JS执行上下文、内存中存储的变量与正在进行的网络请求,单连接PUT上传不可能在刷新后直接保持运行,必须通过分片+断点续传实现用户感知上的进度留存;而SPA内部路由跳转本质是组件卸载与挂载,没有页面级的上下文销毁,只要把上传状态和请求实例抬离页面组件即可实现无中断上传。

一、路由跳转场景:留存上传实例,避免组件卸载中断

这个场景不需要重构现有上传逻辑的核心,只需要解决「上传逻辑与/new-files页面组件强绑定,组件卸载时自动终止请求、丢失进度」的问题:

  • 不要把上传请求实例、实时进度值存在页面组件的useState/useRef中,将这部分逻辑抽到全局状态层:可以用React Context、Redux/Zustand等全局状态方案,存储三类核心数据:当前上传文件的元信息(文件名、大小、唯一标识)、上传请求的XHR/fetch实例(绑定AbortController)、实时上传进度百分比。
  • 上传进度条UI不要硬编码在/new-files页面内部,抽为全局挂载的固定组件(比如挂在路由根布局Layout下),只要全局状态中存在进行中的上传任务就展示,用户跳转至其他路由时也能看到实时进度,返回/new-files页面时直接读取全局状态的进度值渲染即可,不需要重新初始化上传流程。
  • 调整useEffect清理函数的逻辑:不要在组件卸载(路由跳转)时默认执行abortController.abort()中断请求,仅当用户主动触发取消上传、上传完成/失败的场景下才执行请求中断操作。

最简全局状态实现参考(以Zustand为例):

// 全局上传状态store
import { create } from 'zustand'
const useUploadStore = create((set) => ({
  uploadInstance: null, // 存储XHR/AbortController实例
  progress: 0,
  fileMeta: null,
  startUpload: (file) => {
    const xhr = new XMLHttpRequest()
    // 绑定进度事件实时更新全局状态
    xhr.upload.onprogress = (e) => {
      set({ progress: Math.round((e.loaded / e.total) * 100) })
    }
    xhr.open('PUT', 'your-upload-endpoint')
    xhr.send(file)
    xhr.onload = () => set({ progress: 100 })
    set({ 
      uploadInstance: xhr, 
      fileMeta: { name: file.name, size: file.size } 
    })
  },
  clearUploadTask: () => set({ uploadInstance: null, progress: 0, fileMeta: null })
}))

// /new-files页面仅负责触发上传,不持有上传状态
function NewFilesPage() {
  const { startUpload } = useUploadStore()
  return <input type="file" onChange={(e) => startUpload(e.target.files[0])} />
}

// 根布局全局挂载进度条,不随路由切换卸载
function RootLayout() {
  const { progress, uploadInstance } = useUploadStore()
  return (
    <div>
      <Outlet /> {/* react-router-dom路由出口 */}
      {uploadInstance && (
        <div className="global-upload-progress">文件上传中:{progress}%</div>
      )}
    </div>
  )
}

二、页面刷新场景:分片上传+本地持久化断点续传

该场景下原有PUT请求必然会被浏览器终止,核心实现思路是:刷新前持久化已上传进度,刷新后重新初始化上传任务,从上次中断的位置继续传输,实现用户无感知的进度恢复。
实现步骤如下:

  • 替换单PUT全量上传逻辑为分片上传:用Blob.prototype.slice将大文件切为固定大小的分片(单分片大小建议100MB-200MB,平衡请求数与单分片重传成本),先基于文件的大小、最后修改时间、抽样内容生成唯一文件hash,作为续传的匹配标识。
  • 对接服务端实现三个核心接口:分片上传接口、已上传分片查询接口、分片合并接口。每次初始化上传任务时,先传文件hash调用查询接口,拿到已经上传成功的分片列表,跳过这部分分片,仅上传未完成的分片,实现断点续传。
  • 本地持久化上传进度:每完成一个分片的上传,就将当前文件hash、已完成分片索引、进度值存入localStorage或IndexedDB。页面刷新后,用户选择同一文件时先校验hash是否与本地存储的记录匹配,匹配则直接读取已上传分片记录,跳过已传部分继续上传。如果需要实现刷新后不用用户重新选文件,可以用File System Access API持久化文件句柄(需用户授权,兼容Chrome/Edge等Chromium内核浏览器)。
  • 所有分片传输完成后,调用服务端分片合并接口生成完整文件,同时清除本地存储的对应上传进度记录。

如果你当前使用云厂商对象存储服务(OSS、COS等)存储文件,这类服务一般原生提供分片上传、断点续传的SDK能力,直接复用SDK能力即可,不需要从零实现分片逻辑。

三、常见避坑点

  • 不建议用Service Worker尝试实现刷新时请求保活:Service Worker生命周期与浏览器标签页强绑定,页面刷新/关闭时Service Worker会被冻结或终止,且保活逻辑的复杂度远高于分片续传方案,投入产出比极低。
  • 单PUT全量上传不适合1GB以上的大文件场景:一旦出现网络波动就会全量重传,哪怕不需要支持刷新续传,大文件场景也建议默认使用分片上传。
  • 路由跳转场景不需要把上传状态序列化存入sessionStorage/URL参数,全局状态直接持有请求实例的方案性能最优,没有序列化/反序列化开销,进度更新延迟最低。

内容的提问来源于stack exchange,提问作者Kid_Learning_C

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 13:03:21