我想创建一个简单的文件上传器,我已经有了来自dropzone拖放的listOfFiles
。
文件上的简单循环
{listOfFiles.map((file, i) => (
<SingleFile
key={i}
index={i}
file={file}
handleDelete={handleDelete}
/>
))}
在SingleFile
内部,当组件使用useEffect
装载时,我执行axios
API调用以上载文件,简化如下:
useEffect(() => {
if (!props.file) return;
// ...
// Axios call and bunch of states changes.
// ...
}, [props.file]);
上传过程很好&当我拖动另一个文件时,它可以工作,新文件添加到listOfFiles
,然后开始上传。
现在,当我单击SingleFile
中的删除按钮时,父组件上的handleDelete
将触发以删除具有特定索引的文件。
<button onClick={props.handleDelete(index)}> Delete </button>
在handleDelete
上
setListOfFiles((listOfFiles) => listOfFiles.filter((f, i) => i !== index));
现在的问题是,当我删除一个SingleFile
时,listOfFiles
数组会被更新,作为SingleFile
组件的每个数组元素都会被重新渲染,并且上传axios
调用会被再次调用。。。等
当另一个实例发生更改时,是否有任何方法不重新渲染组件的实例?在重新渲染时,只取决于它的状态/道具?并通过父阵列中的唯一id或索引来控制其装载/卸载。
可能会重新渲染,因为每个SingleFile
组件上的key
只是listOfFiles
数组中相应项的索引。React使用key
来跟踪在呈现组件列表时哪个项是哪个项。所以,如果你有:
const listOfFiles = [
'file0',
'file1',
'file2',
'file3',
]
你会有这些成分:
<SingleFile key=0 file='file0' />
<SingleFile key=1 file='file1' />
<SingleFile key=2 file='file2' />
<SingleFile key=3 file='file3' />
键只是数组中的索引。因此,如果删除第二个元素(即索引为"file1"的元素(,react会认为它之后的所有元素都发生了变化(因为它们现在有不同的数据(。你最终得到了这个数组:
const listOfFiles = [
'file0',
// 'file1', // this element was removed
'file2',
'file3',
]
这些成分:
<SingleFile key=0 file='file0' />
<SingleFile key=1 file='file2' />
<SingleFile key=2 file='file3' />
看看密钥1和2现在是如何拥有file2
和file3
的,而之前它们拥有file1
和file2
?React现在认为关键帧为1和2的组件已经更改,并将重新渲染。此外,由于file
已更改,您的useEffect
调用将再次运行。
解决方案是将密钥设置为每个文件唯一的密钥,如果删除其中一个文件,则该密钥不会更改。文件名可能是:
{listOfFiles.map((file, i) => (
<SingleFile
key={file.name}
index={i}
file={file}
handleDelete={handleDelete}
/>
))}
尽管多个文件可能有相同的名称,而且React不允许重复的密钥。一个可能更好的解决方案是添加文件上传时间的时间戳(以微秒为单位(,并将其包含在file
对象中。仍然有重复的可能性,但可能性要小得多:
{listOfFiles.map((file, i) => (
<SingleFile
key={file.uploadedStamp}
index={i}
file={file}
handleDelete={handleDelete}
/>
))}
事实上,也许更好的解决方案是在上传每个文件时,只添加一个随机值作为唯一id。然后使用该唯一id作为您的key
:
{listOfFiles.map((file, i) => (
<SingleFile
key={file.id}
index={i}
file={file}
handleDelete={handleDelete}
/>
))}
您可能还想使用该id来自己识别文件,而不是使用index
:
{listOfFiles.map((file) => (
<SingleFile
key={file.id}
id={file.id}
file={file}
handleDelete={handleDelete}
/>
))}
然后你的按钮和handleDelete
看起来像这样:
<button onClick={() => props.handleDelete(props.id)}> Delete </button>
const handleDelete = (idToRemove) => {
setListOfFiles(listOfFiles.filter((f) => f.id !== idToRemove));
}