Функциональные компоненты, исчезающие с помощью useMemo / React.memo

Я пытаюсь разобраться useMemo (или React.memo), чтобы оптимизировать рендеринг некоторых компонентов.

У меня проблема, которую я не могу объяснить.

У меня есть следующий код:

[...]
    const [ cards, setCards ] = useState<Card[]>([])


    function addCard(){
        setCards([...cards, {name: 'card-' + cards.length, counter: 0, type: 'memo'}])
    }

    function onPressCard(index: number){
        cards[index].counter += 1
        setCards([...cards])
    }

   return (
     [...]
    {
      cards.map((x, index) => 
        <Card key = {index} {...x} onPress = {() => onPressCard(index)}/>
    }
    [...]
)

и Карта определяется как


const Card: FC<CardProps> = function({ name, counter, onPress }){
    const counterRef = useRef(0)

    const item = useMemo(() => {
        counterRef.current +=1

        return (
        <RectButton onPress = {onPress} style = {[styles.card, { backgroundColor: 'lightcoral' }]}>
            <Text style = {styles.text}>{ name }</Text>
            <Text style = {styles.counter}> { `counter ${counter}` }</Text>
            <Text style = {styles.counter}>{ `render: ${counterRef.current}`}</Text>
        </RectButton>
        )
    }, [name, counter])

    return item
}

Почему, когда я нажимаю элемент в списке (кроме последнего), все следующие элементы исчезают? Функциональные компоненты, исчезающие с помощью useMemo / React.memo

Обновлено: то же самое происходит с Карта, определенным как

const areEqual = function(prevProps: Card, nextProps: Card){
    return (
        (prevProps.name === nextProps.name) &&
        (prevProps.counter === nextProps.counter)
    )
}

const Card = React.memo<CardProps>(({ name, counter, onPress }) => {
    const counterRef = useRef(0)

    counterRef.current +=1

    return (
        <RectButton onPress = {onPress} style = {[styles.card, { backgroundColor: 'lightcoral' }]}>
            <Text style = {styles.text}>{ name }</Text>
            <Text style = {styles.counter}> { `counter ${counter}` }</Text>
            <Text style = {styles.counter}>{ `render: ${counterRef.current}`}</Text>
        </RectButton>
        )
}, areEqual)

что делает onPressCard?

Nicholas Tower 12.07.2019 17:33

Я не уверен, что useMemo был разработан для запоминания целых компонентов. Это сильно отличается от React.memo. stackoverflow.com/questions/55466104/…

nubinub 12.07.2019 17:37

@NicholasTower Я обновил свой вопрос

Orelus 12.07.2019 17:40

@nubinub то же самое происходит, когда я использую React.memo с тем же сравнением

Orelus 12.07.2019 17:40
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Навигация по приложениям React: Исчерпывающее руководство по React Router
Навигация по приложениям React: Исчерпывающее руководство по React Router
React Router стала незаменимой библиотекой для создания одностраничных приложений с навигацией в React. В этой статье блога мы подробно рассмотрим...
Массив зависимостей в React
Массив зависимостей в React
Все о массиве Dependency и его связи с useEffect.
4
4
564
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Проблема в том, что мемоизированный компонент содержит ссылку на старую версию onPress. У этого старого onPress есть старая версия cards в его закрытии. Таким образом, нажатие кнопки вызывает старую функцию, которая обновляет родительское состояние на основе этого старого состояния, и в этом старом состоянии меньше элементов.

Один из вариантов исправить это — использовать функциональную версию setCards, чтобы вы основывали обновление на последнем состоянии. Кроме того, я обновил код, чтобы больше не изменять старую карту:

function onPressCard(index: number){
  setCards(oldCards => {
    const newCards = [...oldCards];
    newCards[index] = {...oldCards[index]};
    newCards[index].counter += 1;
    return newCards;
  })
}

Другой вариант — добавить onPress в условия для useMemo, но, поскольку функция onPress постоянно меняется, в итоге вы ничего не получите от мемоизации. Это можно было бы улучшить, если бы сам onPress был запомнен с помощью useCallback:

const onPressCard = useCallback((index: number) => {
  cards[index].counter += 1;
  setCards([...cards]);
}, [cards])

// ...

const item = useMemo(() => {
  counterRef.current +=1

  return ( /* jsx omitted for brevity */ )
}, [name, counter, onPress])    

Отлично, не могли бы вы просто показать пример с использованием useCallback? Не уверен, как это сделать

Orelus 12.07.2019 18:01

@Orelus Я добавил это в. UseCallback очень похож на useMemo., Он просто предполагает, что вы хотите запомнить функцию. useCallback(() => 'hello world') идентично useMemo(() => () => 'hello world');

Nicholas Tower 12.07.2019 18:05

Другие вопросы по теме