【问题标题】:How to avoid re-rendering a functional component when a certain prop changes?当某个道具发生变化时,如何避免重新渲染功能组件?
【发布时间】:2021-01-13 11:15:30
【问题描述】:

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

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

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

当然,结果是cardsInPlay 发生变化,因此每张牌都会重新渲染。如果有一百张牌出现在桌面上,这可能会增加成本。

我怎样才能避免这种情况? 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 数组映射到 JSX 时提供正确的 key 属性以避免不必要地重新挂载实例,并将 Card 组件包装在 React.memo() 中HOC。
  • 这很奇怪,我已经拥有了这两个。
  • 您的意思是const Card = ({ card, cardsInPlay, setCardsInPlay }) =&gt; ...?注意在参数列表中添加了对象解构{ ... }。
  • 哦,是的。我在实际代码中没有任何类似的问题——这只是简化时的一个错字。我现在也将备忘录/密钥添加到示例代码中。
  • 问题是您将 整个 cardsInPlay 数组传递给每个 Card ,因此道具 do 会发生变化。只传递每张卡片需要知道的元素。并使用功能性setCardsInPlay() 签名来访问之前的状态。你不需要传递整个数组来获得它。

标签: javascript reactjs react-props use-state react-state


【解决方案1】:

这取决于您如何将cardsInPlay 传递给每个Card 组件。只要您只将所需的信息传递给孩子,状态中的数组是否发生变化都没有关系。

例如:

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

不会导致重新渲染,因为即使父数组发生变化,实例本身也不会获得新的道具。但是如果你将整个数据传递给每个组件:

<Card cardsInPlay={cardsInPlay} /> 

这将导致所有重新渲染,因为每个Card 都会为每个渲染获得一个新的道具,因为没有两个数组,对象在 Javascript 中是相等的。

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

【讨论】:

  • 谢谢!您的建议的症结正是我最终所做的,结合其他答案建议的 useState 回调签名选项。
【解决方案2】:

问题是您将整个cardsInPlay 数组传递给每个Card,因此React.memo() 仍将重新渲染每张卡片,因为props 已 已更改。只传递每张卡片需要知道的元素,它只会重新渲染已更改的卡片。您可以使用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} />);
});

【讨论】:

  • (在您的代码示例中,Card 仍在使用cardsInPlay,它不再作为道具传入——我猜传入的函数需要处理它而不是传入直接setCardsInPlay。)
  • @temporary_user_name 注意到 cardsInPlay 从传递给 setCardsInPlay 的回调中被接受。试试吧,它会起作用的。
  • @temporary_user_name 你对this很熟悉吧?
  • 我从来不知道。这非常有用。你知道我写了多少反应而没有遇到这个特定方面吗?哇。
  • @temporary_user_name 我写过a lot of react myself(希望你不要介意无耻的插件)。仔细阅读文档是值得的;)
猜你喜欢
  • 2023-02-02
  • 2020-11-16
  • 2021-06-12
  • 2020-10-20
  • 1970-01-01
  • 1970-01-01
  • 2019-08-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多