Официальная документация React напоминает тему контекстов в расширенном разделе, но все приложения немалого размера используют ее, по крайней мере, со сторонними библиотеками. С выпуском React версии 16.3 появился новый Context API, который стал очень простым в использовании. Так почему же это считается сложной темой? Проблема в том, что иногда неясно, для какого конкретного случая использование контекста React подходит действительно хорошо. Неправильное использование этой функции React может вызвать проблемы с производительностью, но я считаю, что если вы поймете все предостережения, вы сможете использовать преимущества контекстов.

Указание значения поставщику

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

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

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

useContext против Consumer

С введением хука useContext использование контекстов внутри функциональных компонентов стало очень удобным. Перед этим перехватчиком для доступа к значению контекста необходимо было обернуть компонент Context.Consumer и передать значение как props. Эти два варианта могут показаться абсолютно идентичными, но имеют разные цели.

Хук следует использовать только в том случае, если значение контекста напрямую связано с состоянием или жизненным циклом компонента. Для рендеринга достаточно компонента Consumer. Давайте посмотрим на пример:

Оба этих случая могут действовать одинаково, но если мы сделаем эти два компонента чистыми, заключив их в React.memo, они будут разными. Тот, который использует useContext, будет повторно отображать весь компонент при изменении значения провайдера. Во втором случае он будет повторно визуализировать не весь компонент, а только его часть, возвращаемую Consumer. Это может показаться преждевременной оптимизацией, но следование лучшим практикам не повредит.

Изоляция путем повторной упаковки с Провайдером

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

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

Итак, когда нам следует использовать контекст React?

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

Использование его вместо redux - ужасная идея из-за поведения, которое я описал выше, но его можно использовать в сочетании с потоками и наблюдаемыми объектами. Хотя с куском обособленных данных пользоваться абсолютно приемлемо.