React Hooks 和 FileReader 的奇怪行为

Weird behaviour with React Hooks and FileReader

我是 React Hooks 的新手,老实说,我不确定这个问题是否与 Hooks 有关,或者我是否只是在做一些普遍错误的事情。

我想构建一个使用 HTML5 FileReader 的图片上传组件,以便在实际发布图片之前向用户展示上传的图片。 以下是我目前所拥有的。

基本上<div id="from-effect"></div>是目前我检查图像是否可以渲染的方式。 我首先想填充此 <div> 而没有副作用(如 <div>I have {files.length} files</div>),但这根本不会对更改做出反应。 下面带有 useEffect 的解决方案正在对变化做出反应。 但是,如果您尝试上传一些图片,您会发现它经常显示错误的结果。

function FileUploader(props) {
  const [files, setFiles] = useState([]);

  const loadImageContent = (name, newFiles) => {
    return (e) => {
      newFiles.push({ name: name, src: e.target.result });
    };
  }

  const handleUpload = async (e) => {
    const newFiles = [];
    for (const file of e.target.files) {
      const reader = new FileReader();
      reader.onload = loadImageContent(file.name, newFiles);
      await reader.readAsDataURL(file);
    }
    setFiles(newFiles);
  }

  useEffect(() => {
    console.log('in use Effect, files:', files);
    const prevCont = document.getElementById("from-effect");
    prevCont.innerHTML = `I have ${files.length} files`;
  });

  return <div>
    <input
      type="file" name="fileUploader" id="fileUploader"
      accept="image/*" multiple="multiple"
      onChange={handleUpload}
    />
    <div id="from-effect"></div>
  </div>;
}

我做错了什么? 或者更好的是,我怎样才能在没有副作用的情况下实现它?

我不确定我是否遵循了您的最终目标,或者当您说要在 POST 上传图片之前向用户显示上传图片时您的意思 - 您是否要自动 POST,还是您希望用户单击 "upload/save/POST" 按钮或其他按钮?

下面是如何显示图像的示例:

编辑: 使事情变得更清楚,添加了 "save" 显示警报的按钮包含您可能用来 POST 返回服务器的数据。此外,为 "JSONify" 文件元数据添加了一个方法,因为我们上传文件的方式不允许我们将 [object File] 本地转换为 JSON.

const { useState } = React;

function FileUploader(props) {
  const [files, setFiles] = useState([]);
  
  const getFileMetadata = file => {
    /**
     * The way we are handling uploads does not allow us to
     * turn the uploaded [object File] into JSON.
     *
     * Therefore, we have to write our own "toJSON()" method.
     */
    return {
      lastModified: file.lastModified,
      name: file.name,
      size: file.size,
      type: file.type,
      webkitRelativePath: file.webkitRelativePath
    }
  }

  const handleUpload = e => {
    let newstate = [];
    for (let i = 0; i < e.target.files.length; i++) {
      let file = e.target.files[i];
      let metadata = getFileMetadata(file);
      let url = URL.createObjectURL(file);
      newstate = [...newstate, { url, metadata }];
    }
    setFiles(newstate);
  };
  
  const handleSave = () => {
    alert(`POST Files Here..\n\n ${JSON.stringify(files,null,2)}`);
  }

  return (
    <div>
      <input type="file" accept="image/*" multiple onChange={handleUpload} />
      <div>
        <button onClick={handleSave} disabled={!(files && files.length > 0)}>
          Save Image(s)
        </button>
      </div>
      {files.map(f => {
        return (
          <div>
            <img src={f.url} height="100" width="100" />
          </div>
        );
      })}
    </div>
  );
}

ReactDOM.render(<FileUploader />, document.body);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.9.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.6/umd/react-dom.production.min.js"></script>