Перехватчики реакции: эффект использования не запускается для массива объектов

#reactjs #react-hooks

#reactjs #реагирующие крючки

Вопрос:

Для удобства чтения я собираюсь убрать большую функциональность из моих примеров. Однако, по сути, у меня есть useEffect (показано ниже) зависимость, которая отслеживает state.cards массив объектов card. Я предполагал, что если это state.cards свойство изменится, то useEffect должно сработать. Однако, это не совсем так.

Ниже приведены два решения, которые используются. Я хочу использовать первый, поскольку он находится в постоянном времени. Второй, хотя и точный, является линейным. Что меня смущает, так это то, почему второй вариант запускает зависимость, а первый — нет. Оба возвращают клон корректно измененного состояния.

Это не запускает зависимость от эффекта использования state.cards .

       const newArr = { ...state };
      const matchingCard = newArr.cards[action.payload];   <-- payload = number
      matchingCard.correct  = 1;
      matchingCard.lastPass = true;
      return newArr;
  

Это действительно запускает зависимость от эффекта использования state.cards .

       const newArr = { ...state };
      const cards = newArr.cards.map((card) => {
        if (card.id === action.payload.id) {
          card.correct  = 1;
          card.lastPass = true;
        }
        return card;
      });
      return { ...newArr, cards };
  

Эффект использования

   useEffect(() => {
    const passedCards = state.cards.filter((card) => {
      return card.lastPass;
    });
    setLearnedCards(passedCards);
    const calculatePercent = () => {
      return (learnedCards.length / state.cards.length) * 100;
    };
    dispatch({ type: 'SET_PERCENT_COMPLETE', payload: calculatePercent() });
  }, [learnedCards.length, state.cards]);
  

Состояние

 const initialState = {
  cards: [],  <-- each card will be an object
  percentComplete: 0,
  lessonComplete: false,
};
  



Решение: Рабочее решение с использованием первого примера:

       const newCardsArray = [...state.cards];
      const matchingCard = newCardsArray[action.payload];
      matchingCard.correct  = 1;
      matchingCard.lastPass = true;
      return { ...state, cards: newCardsArray };
  

Почему: Расширение массива state.cards создает новую мелкую копию этого массива. Затем я могу внести изменения в этот клонированный массив и вернуть его как новое значение, присвоенное state.cards . У расширенного массива есть новая ссылка, и это обнаружено useEffect .

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

1. На что state похоже?

2. @Yousaf обновлен с исходным состоянием

3. Проблема в первом примере кода заключается в том, что он изменяет состояние напрямую. { ...state } — Это не приведет к клонированию объектов внутри state . Это означает, что вы изменяете cards массив напрямую. Это не изменяет state.cards , следовательно useEffect , не запускается. Способ сделать это — сопоставление над state.cards .

4. { ...state } — Это создание нового объекта, копирование свойств state в новый объект, но объекты являются ссылочными типами, поэтому вы в основном создаете свойство с именем cards в новом объекте, но его значением является та же ссылка на массив, на которую указывало cards свойство в state объекте.

5. @Kevin достаточно справедливо. У меня такое чувство, что он улавливает правильное значение при повторном запуске. Вы могли бы просто использовать passedCards.length для передачи правильного значения в том же цикле useEffect.

Ответ №1:

Мое лучшее предположение заключается в том, что во втором рабочем примере .map возвращает новый массив с новой ссылкой. В первом примере вы просто изменяете содержимое массива, но не ссылку на этот массив.

Я не совсем уверен, как сравнивается useEffect, но, если я правильно помню для объекта, все дело в ссылке на этот объект. Что иногда затрудняет использование useEffect для объектов. То же самое может быть и с массивами.

Почему бы вам не попробовать:

 const newCardsArray = [...state.cards]
// do your mutations here
  

следует скопировать массив с новой ссылкой, как вы сделали с объектом.

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

1. «ключи» в массиве? Ваш комментарий не имеет большого смысла, или мне не хватает деталей, которые могли бы вам помочь. Если вы хотите принудительно выполнить обновление, обновите ссылку, которую вы передаете в качестве поддержки другому компоненту. Приведенный выше пример делает это для массивов