ПРЕДИСЛОВИЕ / ОПИСАНИЕ
Я пытаюсь использовать новую функцию хуков React для веб-сайта электронной коммерции, который я создаю, и у меня возникла проблема с устранением ошибки из компонента моей корзины покупок.
Я думаю, уместно предварить обсуждение тем фактом, что я пытаюсь сохранить модульность своего глобального состояния, используя несколько компонентов контекста. У меня есть отдельный компонент контекста для типов товаров, которые я предлагаю, и отдельный компонент контекста для товаров в корзине покупателя.
ПРОБЛЕМА
Проблема, с которой я сталкиваюсь, заключается в том, что когда я отправляю действие для добавления компонента в мою корзину, редуктор будет запускаться дважды, как если бы я дважды добавил элемент в свою корзину. Но только при первоначальном рендеринге или по странным причинам, например, когда отображение установлено на hidden, а затем обратно на block, или для изменения z-index и, возможно, других подобных изменений.
Я знаю, что это довольно многословно, но это довольно сложная проблема, поэтому я создал два кода, которые демонстрируют проблему:
Вы увидите, что я включил кнопку для переключения display компонентов. Это поможет продемонстрировать взаимосвязь css с проблемой.
Наконец, пожалуйста, следите за консолью в ручках кода, это покажет все нажатия кнопок и какая часть каждого редуктора была запущена. Проблемы наиболее очевидны в полный пример, но операторы консоли показывают, что проблема также присутствует в минимальный пример.
ПРОБЛЕМНАЯ ОБЛАСТЬ
Я определил, что проблема связана с тем, что я использую состояние хука useContext для получения списка элементов. Функция вызывается для создания редуктора для моего хука useReducer, но возникает только тогда, когда используется другой хук. АКА Я мог бы использовать функцию, которая не будет подвергаться повторной оценке, как хук, и не иметь проблемы, но я также нужна информация из моего предыдущего контекста, так что обходной путь на самом деле не решает мою проблему.
Соответствующие ссылки
Я определил, что проблема НЕ связана с HTML, поэтому я не буду включать ссылки на исправления HTML, которые я пробовал. Проблема, вызванная css, не связана с css, поэтому я также не буду включать ссылки css.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


Как вы указали, причина та же, что и у моего связанный ответ, на который вы ссылались. Вы воссоздаете свой редюсер всякий раз, когда 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, которые могут быть полезны, если вы не знакомы с тем, как использовать этот хук:
@ Райан еще не нашел времени, чтобы полностью прочитать этот и другой ваш ответ, но, насколько мне известно, это не должно вызывать ошибок, верно? (если ваш редуктор чистый и без побочных эффектов). Потому что, если редуктор вызывается дважды для каждого действия приращения, скажем, второй вызов все равно получает состояние как первый вызов. Возможно, вы захотите лучше подчеркнуть это, хотя вы намекаете на это.
@gmoniava Правильно. Редуктор, вызываемый дважды, должен быть безвредным.
Отделите Редюсер от функционального компонента, который помог мне решить мою проблему.
"Отдельный редуктор" что это значит? Я получаю сообщение об ошибке «React Hook «useReducer» не может быть вызван на верхнем уровне». при попытке отделить редуктор?
Пример, основанный на превосходном ответе Райана.
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, движок рендеринга вызывается дважды.
Вау, этот ответ золотой, очень ценится!