Контекст React¶
Контекст позволяет передавать данные через дерево компонентов без необходимости передавать пропсы на промежуточных уровнях.
В типичном React-приложении данные передаются сверху вниз (от родителя к дочернему компоненту) с помощью пропсов. Однако, этот способ может быть чересчур громоздким для некоторых типов пропсов (например, выбранный язык, UI-тема), которые необходимо передавать во многие компоненты в приложении. Контекст предоставляет способ делиться такими данными между компонентами без необходимости явно передавать пропсы через каждый уровень дерева.
Когда использовать контекст¶
Контекст разработан для передачи данных, которые можно назвать «глобальными» для всего дерева React-компонентов (например, текущий аутентифицированный пользователь, UI-тема или выбранный язык). В примере ниже мы вручную передаём проп theme
, чтобы стилизовать компонент Button:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
|
Контекст позволяет избежать передачи пропсов в промежуточные компоненты:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
|
Перед тем, как вы начнёте использовать контекст¶
Обычно контекст используется, если необходимо обеспечить доступ данных во многих компонентах на разных уровнях вложенности. По возможности не используйте его, так как это усложняет повторное использование компонентов.
Если вы хотите избавиться от передачи некоторых пропсов на множество уровней вниз, обычно композиция компонентов является более простым решением, чем контекст.
Например, давайте рассмотрим компонент Page
, который передаёт пропсы user
и avatarSize
на несколько уровней вниз, чтобы глубоко вложенные компоненты Link
и Avatar
смогли их использовать:
1 2 3 4 5 6 7 8 9 |
|
Передача пропсов user
и avatarSize
вниз выглядит избыточной, если в итоге их использует только компонент Avatar
. Так же плохо, если компоненту Avatar
вдруг потребуется больше пропсов сверху, тогда вам придётся добавить их на все промежуточные уровни.
Один из способов решить эту проблему без контекста — передать вниз сам компонент Avatar
, в случае чего промежуточным компонентам не нужно знать о пропсах user
и avatarSize
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
С этими изменениями, только корневой компонент Page
знает о том, что компоненты Link
и Avatar
используют user
и avatarSize
.
Этот способ может сделать ваш код чище во многих случаях, уменьшая количество пропсов, которые вы должны передавать через ваше приложение, и давая больше контроля корневым компонентам. Однако, это решение не является верным в каждом случае. Перемещая больше сложной логики вверх по дереву, вы перегружаете вышестоящие компоненты.
Вы не ограничены в передаче строго одного компонента. Вы можете передать несколько дочерних компонентов или, даже, создать для них разные «слоты», как показано здесь:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Этого паттерна достаточно для большинства случаев, когда вам необходимо отделить дочерний компонент от его промежуточных родителей. Вы можете пойти ещё дальше, используя рендер-пропсы, если дочерним компонентам необходимо взаимодействовать с родителем перед рендером.
Однако, иногда одни и те же данные должны быть доступны во многих компонентах на разных уровнях дерева и вложенности. Контекст позволяет распространить эти данные и их изменения на все компоненты ниже по дереву. Управление текущим языком, UI темой или кешем данных — это пример тех случаев, когда реализация с помощью контекста будет проще использования альтернативных подходов.
API¶
React.createContext¶
1 |
|
Создание объекта Context. Когда React рендерит компонент, который подписан на этот объект, React получит текущее значение контекста из ближайшего подходящего Provider
выше в дереве компонентов.
Аргумент defaultValue
используется только в том случае, если для компонента нет подходящего Provider
выше в дереве. Это может быть полезно для тестирования компонентов в изоляции без необходимости оборачивать их. Обратите внимание: если передать undefined
как значение Provider
, компоненты, использующие этот контекст, не будут использовать defaultValue
.
Context.Provider¶
1 |
|
Каждый объект Контекста используется вместе с Provider
компонентом, который позволяет дочерним компонентам, использующим этот контекст, подписаться на его изменения.
Принимает проп value
, который будет передан во все компоненты, использующие этот контекст и являющиеся потомками этого Provider компонента. Один Provider может быть связан с несколькими компонентами, потребляющими контекст. Так же Provider компоненты могут быть вложены друг в друга, переопределяя значение контекста глубже в дереве.
Все потребители, которые являются потомками Provider, будут повторно рендериться, как только проп value
у Provider изменится. Потребитель перерендерится при изменении контекста, даже если его родитель, не использующий данный контекст, блокирует повторные рендеры с помощью shouldComponentUpdate
.
Изменения определяются с помощью сравнения нового и старого значения, используя алгоритм, аналогичный Object.is
.
Примечание
Способ, по которому определяются изменения, может вызвать проблемы при передаче объекта в
value
: смотрите Предостережения.
Class.contextType¶
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
В свойство класса contextType
может быть назначен объект контекста, созданный с помощью React.createContext()
. Это позволяет вам использовать ближайшее и актуальное значение указанного контекста при помощи this.context
. В этом случае вы получаете доступ к контексту, как во всех методах жизненного цикла, так и в рендер методе.
Примечание
Вы можете подписаться только на один контекст, используя этот API. В случае, если вам необходимо использовать больше одного, смотрите Использование нескольких контекстов.
Если вы используете экспериментальный синтаксис публичных полей класса, вы можете использовать static поле класса, чтобы инициализировать ваш
contextType
.
1 2 3 4 5 6 7 |
|
Context.Consumer¶
1 2 3 |
|
Consumer
— это React-компонент, который подписывается на изменения контекста. В свою очередь, это позволяет вам подписаться на контекст в функциональном компоненте.
Consumer
принимает функцию в качестве дочернего компонента. Эта функция принимает текущее значение контекста и возвращает React-компонент. Передаваемый аргумент value
будет равен ближайшему (вверх по дереву) значению этого контекста, а именно пропу value
Provider компонента. Если такого Provider компонента не существует, аргумент value
будет равен значению defaultValue
, которое было передано в createContext()
.
Примечание
Подробнее про паттерн «функция как дочерний компонент» можно узнать на странице Рендер-пропсы.
Примеры¶
Динамический контекст¶
Более сложный пример динамических значений для UI темы:
theme-context.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
themed-button.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
Изменение контекста из вложенного компонента¶
Довольно часто необходимо изменить контекст из компонента, который находится где-то глубоко в дереве компонентов. В этом случае вы можете добавить в контекст функцию, которая позволит потребителям изменить значение этого контекста:
theme-context.js
1 2 3 4 5 6 7 |
|
theme-toggler-button.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
app.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
|
Использование нескольких контекстов¶
Чтобы последующие рендеры (связанные с контекстом) были быстрыми, React делает каждого потребителя контекста отдельным компонентом в дереве.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 |
|
Если два или более значений контекста часто используются вместе, возможно, вам стоит рассмотреть создание отдельного компонента, который будет передавать оба значения дочерним компонентам с помощью паттерна «рендер-пропс».
Предостережения¶
Контекст использует сравнение по ссылкам, чтобы определить, когда запускать последующий рендер. Из-за этого существуют некоторые подводные камни, например, случайные повторные рендеры потребителей, при перерендере родителя Provider-компонента. В следующем примере будет происходить повторный рендер потребителя каждый повторный рендер Provider-компонента, потому что новый объект, передаваемый в value
, будет создаваться каждый раз:
1 2 3 4 5 6 7 8 9 10 11 |
|
Один из вариантов решения этой проблемы — хранение этого объекта в состоянии родительского компонента:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Устаревший API¶
Примечание
В прошлом React имел только экспериментальный API контекста. Старый API будет поддерживаться во всех 16.x релизах, но использующие его приложения должны перейти на новую версию. Устаревший API будет удалён в будущем крупном релизе React.