当React中的父数组发生更改时,防止组件作为数组元素重新渲染



我想创建一个简单的文件上传器,我已经有了来自dropzone拖放的listOfFiles

文件上的简单循环

{listOfFiles.map((file, i) => (
<SingleFile
key={i}
index={i}
file={file}
handleDelete={handleDelete}
/>
))}

SingleFile内部,当组件使用useEffect装载时,我执行axiosAPI调用以上载文件,简化如下:

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现在是如何拥有file2file3的,而之前它们拥有file1file2?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));
}

最新更新