#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 (надеюсь, вы не возражаете против бесстыдного плагина). Стоит внимательно прочитать документацию 😉