React Hooks:在组件内部处理异步 setState 的正确方法是什么? (上传进度axios)

React Hooks: what is the correct way to handle async setState inside component ? (uploadProgress axios)

我们想post并行处理多个文件并显示上传进度。

我们第一次尝试解决它是在 uploadProgress 回调中使用 setState 和之前的状态:

const FileUpload = () => {
  const [pendingUploads, setPendingUploads] = useState({});

  // For clarity: files = Record<string, {id: string, progress: string, data: FormData}>
  const handleChange = files => {
    setPendingUploads(files);

    const handleUploadProgress = (progressEvent, id) => {
      const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;

      // Problem: pendingUploads is always empty, we do not get the updated value from last update 
      setPendingUploads({
        ...pendingUploads,
        [id]: {
          ...pendingUploads[id],
          progress: `${percentCompleted}%`
        }
      });
    };

    files.forEach(file => {
      axios.post("/files", file.data, {
        onUploadProgress: e => handleUploadProgress(e, file.id)
      });
    });
  };

  return (
    <>
      <div>
        {_.map(pendingUpload, (pendingUpload, idx) => (
          <div key={idx}>
            {pendingUpload.id}: {pendingUpload.progress}
          </div>
        ))}
      </div>

      <input multiple type="file" onChange={handleChange} />
    </>
  );
};

pendingUploads 始终为空,因此一次仅显示 1 个文件的进度值。

正确的做法是什么?到目前为止,我们有 2 个工作示例:

1) 解决方案 1

const FileUpload = () => {
  const [pendingUploads, setPendingUploads] = useState({});
  const [progressUpdate, setProgressUpdate] = useState({ id: "", value: "" });

  useEffect(() => {
    setPendingUploads({
      ...pendingUploads,
      [progressUpdate.id]: {
        ...pendingUploads[progressUpdate.id],
        progress: progressUpdate.value
      }
    });
  }, [progressUpdate]);

  const handleUploadProgress = (progressEvent, id) => {
    const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
    setProgressUpdate({ id, value: `${percentCompleted}%` });
  };

  // files = Record<string, {id: string, progress: string, data: FormData}>
  const handleChange = files => {
    setPendingUploads(files);
    files.forEach(file => {
      axios.post("/files", file.data, {
        onUploadProgress: e => handleUploadProgress(e, file.id)
      });
    });
  };

  return (
    <div>
      <div>
        {_.map(pendingUploads, (pendingUpload, idx) => (
          <div key={idx}>
            {pendingUpload.id}: {pendingUpload.progress}
          </div>
        ))}
      </div>

      <input multiple type="file" onChange={handleChange} />
    </div>
  );
};



2) 解2

const FileUpload = () => {

  const [pendingUploads, setPendingUploads] = useState({});

  // files = Record<string, {id: string, progress: string, data: FormData}>
  const handleChange = files => {
    const handleUploadProgress = (progressEvent, id) => {
      const percentCompleted = (progressEvent.loaded * 100) / progressEvent.total;
      files[id].progress = `${percentCompleted}%`;
      setPendingUploads({ ...files });
    };

    files.forEach(file => {
      axios.post("/files", file.data, {
        onUploadProgress: e => handleUploadProgress(e, file.id)
      });
    });
  };

  return (
    <>
      <div>
        {_.map(pendingUpload, (pendingUpload, idx) => (
          <div key={idx}>
            {pendingUpload.id}: {pendingUpload.progress}
          </div>
        ))}
      </div>

      <input multiple type="file" onChange={handleChange} />
    </>
  );
};

欢迎任何显示此特定案例最佳实践的反馈或工作示例。

谢谢

您不能在响应回调中使用 pendingUploads 常量,因为它会引用过时的版本。相反,考虑像这样使用以前的状态 setState 签名:setPendingUploads((prevPendingUploads) => {return {...prevPendingUploads, /* updates here */}}