Почему отправка useReducer вызывает повторную визуализацию?

Предположим, я реализую такое простое состояние глобальной загрузки:

// hooks/useLoading.js
import React, { createContext, useContext, useReducer } from 'react';

const Context = createContext();

const { Provider } = Context;

const initialState = {
  isLoading: false,
};

function reducer(state, action) {
  switch (action.type) {
    case 'SET_LOADING_ON': {
      return {
        ...state,
        isLoading: true,
      };
    }
    case 'SET_LOADING_OFF': {
      return {
        ...state,
        isLoading: false,
      };
    }
  }
}

export const actionCreators = {
  setLoadingOn: () => ({
    type: 'SET_LOADING_ON',
  }),
  setLoadingOff: () => ({
    type: 'SET_LOADING_OFF',
  }),
};

export const LoadingProvider = ({ children }) => {
  const [{ isLoading }, dispatch] = useReducer(reducer, initialState);
  return <Provider value={{ isLoading, dispatch }}>{children}</Provider>;
};

export default () => useContext(Context);

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

import React from 'react';
import useLoading, { actionCreators } from 'hooks/useLoading';

export default () => {
  const { dispatch } = useLoading();
  dispatch(actionCreators.setLoadingOn();
  doSomethingAsync().then(() => dispatch(actionCreators.setLoadingOff()))
  return <React.Fragment />;
};

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

Однако при запуске этого кода строка dispatch(actionCreators.setLoadingOn()) запускает обновление глобального состояния и useLoading крюк запускается снова и так же dispatch(actionCreators.setLoadingOn()) (бесконечные рендеры -_-)

Я неправильно понимаю useReducer? Или что-то еще, что я делаю, может вызвать бесконечные повторные рендеры?

2 ответа

Решение

Первая проблема заключается в том, что вы никогда не должны запускать какие-либо обновления состояния React во время рендеринга, включая useReducersс dispatch() а также useStateсеттеры.

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

"Стабильная идентичность" означает, что dispatch переменная будет указывать на одну и ту же ссылку на функцию во всех рендерах.

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

Значение вашего поставщика является объектом (значение ={{ isLoading, dispatch}}). Это означает, что идентичность самого значения изменится при изменении состояния контекста (например, при изменении isLoading). Итак, даже если у вас есть компонент, в котором вы используете только диспетчеризацию, например:

const { dispatch } = useLoading()

Компонент будет повторно отрисован при изменении isLoading.

Если вы находитесь в точке, где вы чувствуете, что повторный рендеринг выходит из-под контроля, способ воспользоваться преимуществом стабильной идентификации отправки - создать двух Provider, один для состояния (в данном случае isLoading) и один для отправки, если вы делаете это, компонент, который нуждается только в отправке, например:

const dispatch = useLoadingDispatch()

Не будет повторно визуализироваться при изменении isLoading.

Обратите внимание, что это может быть чрезмерная оптимизация и в простых сценариях может не стоить того.

Это отличный набор статей для дальнейшего чтения по этой теме:https://kentcdodds.com/blog/how-to-optimize-your-context-valuehttps://kentcdodds.com/blog/how-to-use-react-context-effectively

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