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

时间:2019-10-23 09:55:17

标签: reactjs axios react-hooks setstate

我们要并行发布多个文件并显示上传进度。

我们解决该问题的第一个尝试是在setupProgress回调中使用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 ​​

  • 保留状态为(pendingUploads)的待上传文件的地图
  • 在handleUploadProgress(axios回调)中使用setState触发进度更新(progressUpdate)
  • 当progressUpdate更改时,更新useEffect中的未决上传
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

  • 在uploadProgress回调中更改文件,并使用突变值更新组件的状态
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} />
    </>
  );
};

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

谢谢

1 个答案:

答案 0 :(得分:2)

您不能在响应回调中使用未决的const,因为它将引用一个过时的版本。而是考虑使用以前的状态setState签名,例如:setPendingUploads((prevPendingUploads) => {return {...prevPendingUploads, /* updates here */}}