如何避免在某个 prop 发生变化时重新渲染功能组件?

How to avoid re-rendering a functional component when a certain prop changes?

我有一个 PlayArea 组件,其中有许多 Card 组件作为子组件,用于纸牌游戏。

卡片的位置由 PlayArea 管理,它有一个名为 cardsInPlay 的状态值,它是 CardData 对象的数组,包括位置坐标等。 PlayArea 将 cardsInPlaysetCardsInPlay(来自 useState)传递到每个 Card 子组件中。

卡片是可拖动的,在被拖动时它们会调用 setCardsInPlay 来更新它们自己的位置。

当然,结果是 cardsInPlay 发生变化,因此 每张卡片都会重新渲染。 如果有一百张卡片出现在table.

我怎样才能避免这种情况? PlayArea和Card都是功能组件。

这是该描述的简单代码表示:

const PlayArea = () => {
  const [cardsInPlay, setCardsInPlay] = useState([]);

  return (
    <>
      { cardsInPlay.map(card => (
        <Card
          key={card.id}
          card={card}
          cardsInPlay={cardsInPlay}
          setCardsInPlay={setCardsInPlay} />
      }
    </>
  );
}

const Card = React.memo({card, cardsInPlay, setCardsInPlay}) => {
    const onDrag = (moveEvent) => {
       setCardsInPlay(
         cardsInPlay.map(cardInPlay => {
           if (cardInPlay.id === card.id) {
              return {
               ...cardInPlay,
               x: moveEvent.clientX,
               y: moveEvent.clientY
              };
           }
           return cardInPlay;
        }));
      };

      return (<div onDrag={onDrag} />);
   });

          

这取决于您如何将 cardsInPlay 传递给每个 Card 组件。只要你只传递需要的信息给child.

例如:

<Card positionX={cardsInPlay[card.id].x} positionY={cardsInPlay[card.id].y} /> 

不会导致 re-render,因为即使父数组发生变化,实例本身也不会获得新的 prop。但是,如果您将整个数据传递给每个组件:

<Card cardsInPlay={cardsInPlay} /> 

它会导致所有 re-render 因为每个 Card 都会为每个渲染获得一个新的道具,因为没有两个数组,对象在 Javascript.

中是相等的

P.S : 看到示例代码后编辑

问题是您将整个 cardsInPlay 数组传递给每个 Card,因此 React.memo() 仍将 re-render 每张卡片,因为 props 已经改变了。只传递每张卡片需要知道的元素,它只会 re-render 发生变化的卡片。您可以使用 setCardsInPlay()functional update 签名访问之前的 cardsInPlay:

const PlayArea = () => {
  const [cardsInPlay, setCardsInPlay] = useState([]);
  const cards = cardsInPlay.map(
    card => (
      <Card
        key={card.id}
        card={card}
        setCardsInPlay={setCardsInPlay} />
    )
  );

  return (<>{cards}</>);
};

const Card = React.memo(({ card, setCardsInPlay }) => {
  const onDrag = (moveEvent) => {
    setCardsInPlay(
      cardsInPlay => cardsInPlay.map(cardInPlay => {
        if (cardInPlay.id === card.id) {
          return {
            ...cardInPlay,
            x: moveEvent.clientX,
            y: moveEvent.clientY
          };
        }
        return cardInPlay;
      })
    );
  };

  return (<div onDrag={onDrag} />);
});