Как избежать повторного рендеринга функционального компонента при изменении определенного реквизита?

#javascript #reactjs #react-props #use-state #react-state

#javascript #reactjs #react-props #use-state #реагировать-состояние

Вопрос:

У меня есть компонент 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} />);
   });

          
  

Комментарии:

1. Убедитесь, что при сопоставлении cardsInPlay массива с JSX вы предоставляете надлежащий key реквизит, чтобы избежать ненужного перемонтирования экземпляров, и оберните Card компонент в React.memo() HOC.

2. Что ж, это странно, у меня уже есть оба из них.

3. Вы имели в виду const Card = ({ card, cardsInPlay, setCardsInPlay }) => ... ? Обратите внимание на добавление деструктурирования объекта { ... } в список параметров.

4. О, да. У меня нет подобных проблем в реальном коде — это была просто опечатка при упрощении. Я также добавил memo / key в пример кода здесь.

5.Проблема в том, что вы передаете весь cardsInPlay массив каждому Card , поэтому реквизиты меняются. Передавайте только тот элемент, о котором должна знать каждая карта. И используйте функциональную setCardsInPlay() подпись для доступа к предыдущему состоянию. Вам не нужно передавать весь массив, чтобы получить это.

Ответ №1:

Это зависит от того, как вы переходите cardsInPlay к каждому Card компоненту. Не имеет значения, изменяется ли массив в состоянии, если вы передаете дочернему элементу только необходимую информацию.

Например:

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

не вызовет повторного рендеринга, потому что даже если родительский массив изменяется, сам экземпляр не получает новый реквизит. Но если вы передаете все данные каждому компоненту :

 <Card cardsInPlay={cardsInPlay} /> 
  

это приведет к повторному рендерингу всех, потому что каждый Card получит новый реквизит для каждого рендеринга, поскольку в Javascript нет двух массивов, объекты равны.

P.S: Отредактировано после просмотра примера кода

Комментарии:

1. Спасибо! Суть вашего предложения — это именно то, что я в итоге сделал, в сочетании с опцией подписи обратного вызова useState, предложенной другим ответом.

Ответ №2:

Проблема в том, что вы передаете весь cardsInPlay массив каждому Card , поэтому React.memo() все равно будете повторно отображать каждую карту, потому props что они изменились. Передавайте только тот элемент, о котором должна знать каждая карта, и он будет повторно отображать только карту, которая изменилась. Вы можете получить доступ к предыдущему cardsInPlay , используя функциональную подпись обновления setCardsInPlay() :

 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} />);
});
  

Комментарии:

1. (В вашем примере кода Card все еще используется cardsInPlay , который больше не передается как реквизит — я думаю, переданная функция должна обрабатывать это вместо setCardsInPlay прямой передачи.)

2. уведомление @temporary_user_name, cardsInPlay принятое при обратном вызове, переданном setCardsInPlay . Просто попробуйте, это сработает.

3. @temporary_user_name вы знакомы с этим , верно?

4. Я никогда, никогда не знал этого. Это невероятно полезно. Знаете ли вы, сколько реактов я написал, никогда не сталкиваясь с этим конкретным аспектом? Вау.

5. @temporary_user_name Я сам написал много react (надеюсь, вы не возражаете против бесстыдного плагина). Стоит внимательно прочитать документацию 😉