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

IT技术 javascript reactjs
2021-05-01 05:50:30

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

文件的简单循环

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

组件挂载时SingleFile我执行axiosAPI 调用以上传文件,使用useEffect简化如下:

useEffect(() => {
  if (!props.file) return;
  // ...
    // Axios call and bunch of states changes.
  // ...
}, [props.file]);

上传过程很好,当我拖动另一个文件时,它可以工作,添加新文件listOfFiles然后开始上传。

现在,当我单击里面的删除按钮时SingleFilehandleDelete父组件会触发以删除具有特定索引的文件。

<button onClick={props.handleDelete(index)}> Delete </button>

handleDelete

setListOfFiles((listOfFiles) => listOfFiles.filter((f, i) => i !== index));

现在的问题是,当我删除SingleFilelistOfFiles数组被更新,这是一个每个数组元素SingleFile成分进行重新渲染和上传axios再次被调用...等。

当另一个实例发生变化时,有没有办法不重新渲染组件的实例?让它在重新渲染时只取决于它的状态/props?并通过父数组中的唯一 id 或索引来控制它的挂载/卸载。

1个回答

它可能会重新渲染,因为key每个SingleFile组件上的 只是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' />

键只是数组中的索引。因此,如果您删除假设第二个元素(即索引为 1 的元素,'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,以前它们具有file1file2React 现在认为键 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));
}