【问题标题】:Avoid rerendering a list in React Hooks避免在 React Hooks 中重新渲染列表
【发布时间】:2020-05-25 13:05:55
【问题描述】:

我有一个通过map 函数在页面加载时呈现的图像列表。我希望能够选择一张或多张图像,并突出显示它们。此外,所选图像的标题将显示在页面顶部。我遇到的问题是每次选择图像时列表似乎都会重新呈现。这是组件:

export default function App() {
  const [images, setImages] = React.useState<Image[]>([]);
  const [selected, setSelected] = React.useState<boolean>(false);
  const [imageTitles, setImageTitles] = React.useState<string[]>([]);

  const handleImageSelection = (title: string, index: number) => {
    setSelected(!selected);
    const imageExists = imageTitles.indexOf(title) !== -1
    if (!imageExists) {
      setImageTitles([...imageTitles, title]);
    } else {
      setImageTitles(imageTitles.filter(str => str !== title));
    }
  }

  return (
    <div>
      {console.log(images)} // The images array renders every time
      <div>{imageTitles.map(title => <p>{title}</p>)}</div>

      <section className="images">{
        images.map(({ title, image}, index) =>
          <img
            style={{
              marginBottom: 4,
              border: imageTitles.indexOf(title) !== -1 ?
                "4px solid blue" : "", // A selected image is highlighted
              borderRadius: 16
            }}
            src={image}
            alt={image}
            onClick={() => handleImageSelection(title, index)}
          />
        )
      }</section>
    </div>
  );
}

我想知道是不是因为每次选择/取消选择图像时我都在更改 imageTitles 数组的大小(以及 index 值)。

我也尝试过useCallback 像这样:

const handleImageSelection = React.useCallback((title: string, index: number) => {
  setSelected(!selected);
  const imageExists = imageTitles.indexOf(title) !== -1
  if (!imageExists) {
    setImageTitles([...imageTitles, title]);
  } else {
    setImageTitles(imageTitles.filter(str => str !== title));
  }
}, [selected, imageTitles])

但它似乎没有奏效。我的猜测是因为imagetitles 每次都在变化。

那么,是否有可能(出于性能原因)避免在每次选择/取消选择图像时重新渲染图像列表?

【问题讨论】:

    标签: onclick react-hooks memoization map-function usecallback


    【解决方案1】:

    由于您在 useCallback 的依赖项中添加了 selected 和 imageTitles,因此每次调用 useCallback 时都会重新创建它,因为它本身会设置 selected 和 imageTitles 状态

    这里的解决方案是使用 setState 回调模式并将空数组作为依赖传递给 useCallback

    const handleImageSelection = React.useCallback((title: string, index: number) => {
      setSelected(prevSelected => !prevSelected);
    
    
        setImageTitles(prevImageTitles => {
            const imageExists = prevImageTitles.indexOf(title) !== -1;
            if (!imageExists) { 
               return [...prevImageTitles, title];
            } else {
               return prevImageTitles.filter(str => str !== title));
            }
        });
    
    }, []);
    

    另请注意,每次您设置正常行为的状态时,列表都会重新呈现。但是 react 会优化渲染,只会重新渲染那些需要更改的元素。

    这里要注意的另一件事是,您必须将 key prop 传递给映射值,以便 React 进一步优化重新渲染

    return (
        <div>
          <div>{imageTitles.map(title => <p key={title}>{title}</p>)}</div>
    
          <section className="images">{
            images.map(({ title, image}, index) =>
              <img
                key={title} // any unique value here. If you don't have anything use index
                style={{
                  marginBottom: 4,
                  border: imageTitles.indexOf(title) !== -1 ?
                    "4px solid blue" : "", // A selected image is highlighted
                  borderRadius: 16
                }}
                src={image}
                alt={image}
                onClick={() => handleImageSelection(title, index)}
              />
            )
          }</section>
        </div>
      );
    

    【讨论】:

    • 感谢您的帮助!我实现了您的所有更改,但是,当我在 &lt;section&gt; 标记上方执行 {console.log(images)} 时,每次选择/取消选择图像时,我仍然会在控制台中看到图像数组。这是否意味着组件每次都在重新渲染?
    • 是的,这会发生。正如我所说,因为您正在设置状态并且状态更改会触发重新渲染
    • 您说得对,状态更改会导致重新渲染,那么有没有办法在不重新渲染整个图像列表的情况下完成此操作(选择/取消选择单个图像)?
    • 您可以通过将所有图像分离到不同的组件中来避免重新渲染所有图像。但是,正如我所说,如果您正确提供键值,则不需要该优化,因为它已经通过 react 完成。 React 只会对 dom 上实际需要更改的图像进行更改
    • 是的,我已经在两个迭代器中添加了key={title}。只是为了验证我理解这里发生了什么,当我看到 images 数组每次都被记录到控制台时,这意味着该组件实际上是在每次选择/取消选择时重新呈现图像列表,对吗?跨度>
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-12-21
    • 1970-01-01
    • 2023-03-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-21
    相关资源
    最近更新 更多