2

我面临一个问题,可能是一个非常简单的问题,但我从几个小时以来就陷入困境,所以我想寻求你的帮助。

我有一个简单的文件输入,我想将此文件设置为状态,以便稍后在提交表单时上传文件。

const [inputTattoos, setInputTattoos] = useState<[{}]>();

const handleImageChange = async ({
    currentTarget: input,
  }: React.ChangeEvent<HTMLInputElement>) => {
    if (input.files === null) return;

    console.log(input.files);

    setInputTattoos([{ ...input.files }]);

使用此代码,我可以将文件写入状态,但这不是我想要将其存储在 State 上的方式,因为我的 State 看起来像这样:

在此处输入图像描述

我有一个数组,里面有一个带有对象的对象。我实际上从 input.files 得到的只是一个包含对象的数组,但我无法像在控制台上那样存储这个 input.files。我尝试了很多解决方案,但这是我发现可行的唯一方法。对于其他解决方案,我总是在 State 中得到一个空对象或 FileList(undefined),例如使用这个解决方案:

const [inputTattoos, setInputTattoos] = useState<FileList>()
const handleImageChange = async ({
    currentTarget: input,
  }: React.ChangeEvent<HTMLInputElement>) => {
    if (input.files === null) return;

    console.log(input.files);

setInputTattoos(input.files);

这里有什么问题?谢谢!

4

1 回答 1

3

我只会存储文件对象,没有对象包装器或任何东西:

const [inputTattoos, setInputTattoos] = useState<File[]>([]);
// −−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−−^^^^^^−−^^
const handleImageChange = ({
  currentTarget: {files},
}: React.ChangeEvent<HTMLInputElement>) => {
    if (files && files.length) {
        setInputTattoos(existing => [...existing, ...files]);
// −−−−−−−−−−−−−−−−−−−−−^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
    }
    // ...
}

对此有几点说明:

  1. 我已经删除了async。更改处理程序不是async函数,并且不会使用async函数返回的承诺。

  2. 我已经将files属性解构为它自己的参数,以便 TypeScript 知道它不能在警卫和状态设置器回调之间更改。(这也很重要,因为你不应该从 React 的合成事件对象中异步访问属性,除非你调用persist.)

  3. 我使用了状态设置器的回调版本。在根据其现有值(在本例中为先前的内容)设置状态项时,这一点很重要。

  4. 以上依赖于files(来自输入)是可迭代的,它在现代浏览器中是可迭代的,但在一些稍旧的浏览器中却不是。

关于#4,如果您需要为稍旧的浏览器解决这个问题:

const [inputTattoos, setInputTattoos] = useState<File[]>([]);
const handleImageChange = ({
  currentTarget: {files},
}: React.ChangeEvent<HTMLInputElement>) => {
    if (files && files.length) {
        setInputTattoos(existing => existing.concat(Array.from(files))); // *** Only change is here
    }
    // ...
}

那里的变化是回调:

existing => existing.concat(Array.from(files))

请注意,由于files是 a FileList,而不是数组,因此我们需要将其转换为数组concat以正确处理它。

Array.from才几岁,但很容易填充;如果您不想这样做,这里有一个不使用任何现代(箭头功能除外)的替代方法:

existing => existing.concat(Array.prototype.slice.call(files))

Array.from这是用于该部分的完整示例:

const { useState } = React;

function Example() {
    const [inputTattoos, setInputTattoos] = useState/*<File[]>*/([]);
    const [inputKey, setInputKey] = useState(0);
    const handleImageChange = ({
      currentTarget: {file},
    }/*: React.ChangeEvent<HTMLInputElement>*/) => {
        if (files && files.length) {
            setInputTattoos(existing => existing.concat(Array.from(files)));
        }
        // Reset the input by forcing a new one
        setInputKey(key => key + 1);
    }

    return (
        <form>
            Selected tattoos ({inputTattoos.length}):
            <ul>
                {inputTattoos.map((file, index) =>
                    <li key={index}>{file.name}</li>
                )}
            </ul>
            Add: <input key={inputKey} type="file" onChange={handleImageChange} />
        </form>
    );
}

ReactDOM.render(<Example/>, document.getElementById("root"));
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.13.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.13.0/umd/react-dom.production.min.js"></script>

这是一个正在运行的 TypeScript 版本

于 2020-08-20T08:31:09.657 回答