React useReducer Hook срабатывает дважды / как передать реквизит редуктору?

ПРЕДИСЛОВИЕ / ОПИСАНИЕ

Я пытаюсь использовать новую функцию хуков React для веб-сайта электронной коммерции, который я создаю, и у меня возникла проблема с устранением ошибки из компонента моей корзины покупок.

Я думаю, уместно предварить обсуждение тем фактом, что я пытаюсь сохранить модульность своего глобального состояния, используя несколько компонентов контекста. У меня есть отдельный компонент контекста для типов товаров, которые я предлагаю, и отдельный компонент контекста для товаров в корзине покупателя.

ПРОБЛЕМА

Проблема, с которой я сталкиваюсь, заключается в том, что когда я отправляю действие для добавления компонента в мою корзину, редуктор будет запускаться дважды, как если бы я дважды добавил элемент в свою корзину. Но только при первоначальном рендеринге или по странным причинам, например, когда отображение установлено на hidden, а затем обратно на block, или для изменения z-index и, возможно, других подобных изменений.

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

полный пример

минимальный пример

Вы увидите, что я включил кнопку для переключения display компонентов. Это поможет продемонстрировать взаимосвязь css с проблемой.

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

ПРОБЛЕМНАЯ ОБЛАСТЬ

Я определил, что проблема связана с тем, что я использую состояние хука useContext для получения списка элементов. Функция вызывается для создания редуктора для моего хука useReducer, но возникает только тогда, когда используется другой хук. АКА Я мог бы использовать функцию, которая не будет подвергаться повторной оценке, как хук, и не иметь проблемы, но я также нужна информация из моего предыдущего контекста, так что обходной путь на самом деле не решает мою проблему.

Соответствующие ссылки

Я определил, что проблема НЕ связана с HTML, поэтому я не буду включать ссылки на исправления HTML, которые я пробовал. Проблема, вызванная css, не связана с css, поэтому я также не буду включать ссылки css.

Действие useReducer отправлено дважды

Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
20
0
10 463
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

Как вы указали, причина та же, что и у моего связанный ответ, на который вы ссылались. Вы воссоздаете свой редюсер всякий раз, когда Provider повторно рендерится, поэтому в некоторых случаях React выполнит редьюсер, чтобы определить, нужно ли ему повторно рендерить Provider, и если ему нужно повторно рендерить, он обнаружит, что редуктор изменен, поэтому React необходимо выполнить новый редьюсер и использовать созданное им новое состояние, а не то, что было возвращено предыдущей версией редьюсера.

Когда вы не можете просто переместить редьюсер из функционального компонента из-за зависимости от свойств, контекста или другого состояния, решение состоит в том, чтобы запомнить ваш редюсер с помощью useCallback, чтобы вы создавали новый редуктор только при изменении его зависимостей (например, productsList в твоем случае).

Еще одна вещь, о которой следует помнить, это то, что вам не следует слишком беспокоиться о том, что ваш редуктор будет выполняться дважды для одной отправки. Предположение, которое делает React, состоит в том, что редьюсеры, как правило, будут достаточно быстрыми (они не могут ничего делать с побочными эффектами, выполнять вызовы API и т. д.), поэтому стоит рисковать повторным выполнением их в определенных сценариях в чтобы попытаться избежать ненужных повторных рендеров (что может быть намного дороже, чем редюсер, если под элементом с редуктором находится большая иерархия элементов).

Вот модифицированная версия Provider с использованием useCallback:

const Context = React.createContext();
const Provider = props => {
  const memoizedReducer = React.useCallback(createReducer(productsList), [productsList])
  const [state, dispatch] = React.useReducer(memoizedReducer, []);

  return (
    <Context.Provider value = {{ state, dispatch }}>
      {props.children}
    </Context.Provider>
  );
}

Вот модифицированная версия вашего кода: https://codepen.io/anon/pen/xBdVMp?editors=0011

Вот пара ответов, связанных с useCallback, которые могут быть полезны, если вы не знакомы с тем, как использовать этот хук:

Вау, этот ответ золотой, очень ценится!

Spencer Duball 08.03.2019 06:58

@ Райан еще не нашел времени, чтобы полностью прочитать этот и другой ваш ответ, но, насколько мне известно, это не должно вызывать ошибок, верно? (если ваш редуктор чистый и без побочных эффектов). Потому что, если редуктор вызывается дважды для каждого действия приращения, скажем, второй вызов все равно получает состояние как первый вызов. Возможно, вы захотите лучше подчеркнуть это, хотя вы намекаете на это.

Giorgi Moniava 05.02.2021 19:41

@gmoniava Правильно. Редуктор, вызываемый дважды, должен быть безвредным.

Ryan Cogswell 05.02.2021 19:47

Отделите Редюсер от функционального компонента, который помог мне решить мою проблему.

"Отдельный редуктор" что это значит? Я получаю сообщение об ошибке «React Hook «useReducer» не может быть вызван на верхнем уровне». при попытке отделить редуктор?

benishky 18.02.2021 17:16

Пример, основанный на превосходном ответе Райана.

  const memoizedReducer = React.useCallback((state, action) => {
    switch (action.type) {
      case "addRow":
        return [...state, 1];
      case "deleteRow":
        return [];
      default:
        throw new Error();
    }
  }, []) // <--- if you have vars/deps inside the reducer that changes, they need to go here

  const [data, dispatch] = React.useReducer(memoizedReducer, _data);

Когда я прочитал исходный код useContext, я обнаружил

const useContext = hook(class extends Hook {
  call() {
    if (!this._ranEffect) {
      this._ranEffect = true;
      if (this._unsubscribe) this._unsubscribe();
      this._subscribe(this.Context);
      this.el.update();
    }
  }

После первого обновления ставится лайк effect после обновления. После того, как value подписывается на правильный контекст, например, разрешая значение из Provider, он запрашивает обновление еще один. Это не цикл, благодаря флагу _ranEffect.

Мне кажется, что если вышесказанное верно для React, движок рендеринга вызывается дважды.

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