Попробуйте react-tracked и reactive-react-redux для отслеживания использования состояния с помощью перехватчиков React.

Вступление

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

Однако useContext не предназначен специально для глобального состояния, и есть предостережение: любое изменение значения контекста распространяется на все useContext для повторной визуализации компонентов.

В этом посте показан пример кода проблемы и решения с отслеживанием использования состояния.

Проблема

Предположим, что человек объект как состояние:

const initialState = {
  firstName: 'Harry',
  familyName: 'Potter',
};

Мы используем контекст и локальное состояние:

const PersonContext = createContext(null);
const PersonProvider = ({ children }) => {
  const [person, setPerson] = useState(initialState);
  return (
    <PersonContext.Provider value={[person, setPerson]}>
      {children}
    </PersonContext.Provider>
  );
};

Наконец, вот компонент для отображения имени человека:

const DisplayFirstName = () => {
  const [person] = useContext(PersonContext);
  return (
    <div>First Name: {person.firstName}</div>
  );
};

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

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

Решение: отслеживание использования состояния

Давайте посмотрим, как отслеживание использования состояния решает эту проблему.

Провайдер выглядит немного иначе:

const usePerson = () => useState(initialState);
const { Provider, useTracked } = createContainer(usePerson);
const PersonProvider = ({ children }) => (
  <Provider>
    {children}
  </Provider>
);

Компонент DisplayFirstName будет изменен следующим образом:

const DisplayFirstName = () => {
  const [person] = useTracked();
  return (
    <div>First Name: {person.firstName}</div>
  );
};

Заметили изменение? Единственная разница - useTracked() вместо useContext(...).

С этим небольшим изменением отслеживается использование состояния в DisplayFirstName. Даже если имя семейства обновлено, этот компонент не будет повторно визуализироваться, пока не обновляется имя.

Это простая оптимизация рендеринга.

Расширенный пример

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

Вот еще один пример, в котором useTracked намного проще.

const initialState = {
  firstName: 'Harry',
  familyName: 'Potter',
  showFullName: false,
};

Предположим, у нас есть состояние, подобное приведенному выше. Давайте создадим компонент с условием.

const DisplayPersonName = () => {
  const [person] = useTracked();
  return (
    <div>
      {person.showFullName ? (
        <span>
          Full Name: {person.firstName}
          <Divider />
          {person.familyName}
        </span>
      ) : (
        <span>First Name: {person.firstName}</span>
      )}
    </div>
  );
};

Этот компонент будет повторно отрисован в двух сценариях.

  • а) при обновлении firstName или familyName, если отображается полное имя
  • б) при обновлении firstName, если не отображается полное имя

Воспроизвести такое же поведение с useSelector будет непросто и, вероятно, закончится разделением компонентов.

Проекты, использующие отслеживание использования состояния

Есть два проекта, использующих отслеживание использования состояния.

реактивная реакция-редукция



Это альтернативная библиотека для react-redux. Он имеет тот же API хуков и useTrackedState хук.

с отслеживанием реакции



Это библиотека без зависимости от Redux. Пример в этом посте основан на этом. У него есть совместимый API хуков с reactive-react-redux.

Заключительные примечания

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

Технически есть два препятствия. Короче говоря, мы используем Proxy API для отслеживания использования состояния, и мы используем недокументированную функцию в Context API, чтобы остановить распространение. Если вас интересуют эти внутренние компоненты, посетите репозитории GitHub.