Хук useContext — JS: React Hooks
React передает данные внутрь компонентов с верхних уровней на нижние, используя пропсы.
Такой подход неудобен при работе с глобальными данными, которые нужны одновременно во многих компонентах на разных уровнях иерархии. К таким данным относится текущий уровень, текущая тема (темная или светлая) и так далее. Напрямую передавать такие данные неудобно, придется протаскивать их сквозь все приложение.
В таких случаях React позволяет передать данные в приложение и получить внутри любого компонента доступ к этим данным напрямую, минуя пропсы. Этот механизм называется контекст.
Хук useContext() позволяет использовать контекст внутри компонента. Для этого нужно выполнить три действия:
-
Инициализировать контекст в том же месте, где инициализируется приложение
// Параметром передается значение по умолчанию // Имя контекста выбирается произвольно const UserContext = React.createContext(<>);
// user — данные которые лежат внутри контекста UserContext.Provider value=user>> MyComponent /> UserContext.Provider>
import React, useContext > from 'react'; const MyComponent = () => // Возвращает контекст целиком const user = useContext(UserContext); return h1>user.name>h1>; >
Вот другой пример контекста, в котором хранится текущая тема:
const themes = light: foreground: '#000000', background: '#eeeeee', >, dark: foreground: '#ffffff', background: '#222222', >, >; const ThemeContext = React.createContext(<>);
И где-то внутри приложения:
ThemeContext.Provider value=/* текущая тема */>> Content /> ThemeContext.Provider>
Метод React.createContext() принимает значение по умолчанию. Это значение будет передаваться в контекст тех компонентов, которые не обернуты в провайдер. Обычно провайдер всегда используется, чтобы оборачивать все приложение. Поэтому компоненты всегда принимают в контексте значение, переданное провайдером. Но если мы работаем с компонентом вне провайдера, может понадобиться такое значение по умолчанию. Например, такая ситуация может сложиться при тестировании компонента отдельно от приложения.
Внутри контекста может храниться как примитивное значение, так и объект. Изменение содержимого такого объекта никак не отслеживается React, поэтому не приводит к перерендеру. Но если заменить сам объект, то из-за изменившейся ссылки React узнает об изменении и выполнит перерисовку компонентов внутри провайдера.
Иногда возникает ситуация, когда в контексте нужно хранить динамические данные. Например, при авторизации. Когда пользователь авторизован, мы должны сохранить какие-то данные, чтобы пользователю предоставлялись дополнительные функции. Сам по себе контекст для этого ничего не предоставляет, но можно передать в контекст методы для манипулирования данными, а сами данные хранить с помощью useState() . Более продвинутый вариант — это создать провайдер в отдельном компоненте. Так мы сможем изолировать данные от всего приложения, а в компоненты передавать интерфейс для взаимодействия с данными.
Для этого создадим контекст в отдельном модуле, чтобы все компоненты могли его импортировать:
// ThemeContext.js import createContext > from 'react'; export default createContext( themes: <>, theme: <>, setTheme: () => <>, >);
Создаем отдельный компонент провайдера:
import ThemeContext from './ThemeContext.js'; const themes = light: foreground: '#000000', background: '#eeeeee', >, dark: foreground: '#ffffff', background: '#222222', >, >; const ThemeProvider = ( children >) => const [theme, setTheme] = useState(themes.dark); const setLightTheme = () => setTheme(themes.light); const setDarkTheme = () => setTheme(themes.dark); return ( ThemeContext.Provider value= theme, setLightTheme, setDarkTheme, themes >>> children> ThemeContext.Provider> ); >;
И внутри приложения используем провайдер как обычный компонент:
ThemeProvider> MyComponent /> ThemeProvider>
В самом компоненте теперь можно импортировать контекст и использовать функции из провайдера для изменения состояния в контексте:
import React, useContext > from 'react'; import ThemeContext from './ThemeContext.js'; const MyComponent = () => const setLightTheme > = useContext(ThemeContext); return button onClick=() => setLightTheme()>>Включить светлую темуbutton>; >
Открыть доступ
Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно
- 130 курсов, 2000+ часов теории
- 1000 практических заданий в браузере
- 360 000 студентов
Наши выпускники работают в компаниях:
useContext¶
useContext — это хук React, который позволяет вам читать и подписываться на context из вашего компонента.
const value = useContext(SomeContext);
Описание¶
useContext(SomeContext) ¶
Вызовите useContext на верхнем уровне вашего компонента для чтения и подписки на контекст.
1 2 3 4 5 6
import useContext > from 'react'; function MyComponent() const theme = useContext(ThemeContext); // . >
Параметры¶
- SomeContext : Контекст, который вы ранее создали с помощью createContext . Сам контекст не хранит информацию, он только представляет тип информации, которую вы можете предоставить или прочитать из компонентов.
Возвращает¶
useContext возвращает значение контекста для вызывающего компонента. Оно определяется как значение , переданное ближайшему SomeContext.Provider над вызывающим компонентом в дереве. Если такого провайдера нет, то возвращаемое значение будет defaultValue , которое вы передали в createContext для этого контекста. Возвращаемое значение всегда актуально. React автоматически перерисовывает компоненты, которые читают некоторый контекст, если он меняется.
Ограничения¶
- Вызов useContext() в компоненте не влияет на провайдеров, возвращаемых из этого же компонента. Соответствующий должен находиться выше компонента, выполняющего вызов useContext() .
- React автоматически перерисовывает все дочерние компоненты, использующие определенный контекст, начиная с провайдера, получившего другое значение . Предыдущее и последующее значения сравниваются с помощью сравнения Object.is . Пропуск повторных рендеров с помощью memo не мешает дочерним компонентам получать свежие значения контекста.
- Если ваша система сборки выдает дубликаты модулей на выходе (что может произойти при использовании симлинков), это может нарушить контекст. Передача чего-либо через контекст работает только в том случае, если SomeContext , который вы используете для предоставления контекста, и SomeContext , который вы используете для его чтения, являются точно одним и тем же объектом, что определяется сравнением === .
Использование¶
Передача данных глубоко в дерево¶
Вызовите useContext на верхнем уровне вашего компонента для чтения и подписки на контекст.
1 2 3 4 5 6
import useContext > from 'react'; function Button() const theme = useContext(ThemeContext); // . >
useContext возвращает значение контекста для контекста, который вы передали. Чтобы определить значение контекста, React просматривает дерево компонентов и находит ближайшего поставщика контекста выше для данного контекста.
Чтобы передать контекст Button , оберните его или один из его родительских компонентов в соответствующий провайдер контекста:
1 2 3 4 5 6 7 8 9 10 11
function MyPage() return ( ThemeContext.Provider value="dark"> Form /> /ThemeContext.Provider> ); > function Form() // . renders buttons inside . >
Не имеет значения, сколько слоев компонентов находится между провайдером и Button . Когда Button в любом месте внутри Form вызывает useContext(ThemeContext) , он получит «dark» в качестве значения.
useContext() всегда ищет ближайшего провайдера выше компонента, который его вызывает. Она ищет вверх и не рассматривает провайдеров в компоненте, из которого вы вызываете useContext() .
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
import createContext, useContext > from 'react'; const ThemeContext = createContext(null); export default function MyApp() return ( ThemeContext.Provider value="dark"> Form /> /ThemeContext.Provider> ); > function Form() return ( Panel title="Welcome"> Button>Sign up/Button> Button>Log in/Button> /Panel> ); > function Panel( title, children >) const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( section className=className>> h1>title>/h1> children> /section> ); > function Button( children >) const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( button className=className>>children>/button> ); >
Обновление данных, переданных через контекст¶
Часто требуется, чтобы контекст менялся с течением времени. Чтобы обновить контекст, объедините его с state. Объявите переменную state в родительском компоненте и передайте текущее состояние как значение контекста провайдеру.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
function MyPage() const [theme, setTheme] = useState('dark'); return ( ThemeContext.Provider value=theme>> Form /> Button onClick= => setTheme('light'); >> > Switch to light theme /Button> /ThemeContext.Provider> ); >
Теперь любой Button внутри провайдера будет получать текущее значение theme . Если вы вызовете setTheme для обновления значения темы , которое вы передаете провайдеру, все компоненты Button будут перерисованы с новым значением ‘light’ .
Примеры обновления контекста¶
1. Обновление значения через контекст¶
В этом примере компонент MyApp хранит переменную состояния, которая затем передается провайдеру ThemeContext . Установка флажка «Темный режим» обновляет состояние. Изменение предоставленного значения пересматривает все компоненты, использующие данный контекст.
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 53 54
import createContext, useContext, useState > from 'react'; const ThemeContext = createContext(null); export default function MyApp() const [theme, setTheme] = useState('light'); return ( ThemeContext.Provider value=theme>> Form /> label> input type="checkbox" checked=theme === 'dark'> onChange=<(e) => setTheme( e.target.checked ? 'dark' : 'light' ); >> /> Use dark mode /label> /ThemeContext.Provider> ); > function Form( children >) return ( Panel title="Welcome"> Button>Sign up/Button> Button>Log in/Button> /Panel> ); > function Panel( title, children >) const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( section className=className>> h1>title>/h1> children> /section> ); > function Button( children >) const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( button className=className>>children>/button> ); >
Обратите внимание, что value=»dark» передает строку «dark» , а value= передает значение переменной JavaScript theme с фигурными скобками JSX. Фигурные скобки также позволяют передавать контекстные значения, которые не являются строками.
2. Обновление объекта через контекст¶
В этом примере есть переменная состояния currentUser , которая содержит объект. Вы объединяете < currentUser, setCurrentUser >в один объект и передаете его вниз через контекст внутри value=<> . Это позволяет любому компоненту ниже, например, LoginButton , читать и currentUser , и setCurrentUser , а затем вызывать setCurrentUser , когда это необходимо.
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 53 54 55 56 57 58 59 60 61 62
import createContext, useContext, useState > from 'react'; const CurrentUserContext = createContext(null); export default function MyApp() const [currentUser, setCurrentUser] = useState(null); return ( CurrentUserContext.Provider value= <currentUser, setCurrentUser, >> > Form /> /CurrentUserContext.Provider> ); > function Form( children >) return ( Panel title="Welcome"> LoginButton /> /Panel> ); > function LoginButton() const currentUser, setCurrentUser > = useContext( CurrentUserContext ); if (currentUser !== null) return p>You logged in as currentUser.name>./p>; > return ( Button onClick= => setCurrentUser( name: 'Advika' >); >> > Log in as Advika /Button> ); > function Panel( title, children >) return ( section className="panel"> h1>title>/h1> children> /section> ); > function Button( children, onClick >) return ( button className="button" onClick=onClick>> children> /button> ); >
3. Множественные контексты¶
В этом примере есть два независимых контекста. ThemeContext предоставляет текущую тему, которая является строкой, а CurrentUserContext содержит объект, представляющий текущего пользователя.
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
import createContext, useContext, useState > from 'react'; const ThemeContext = createContext(null); const CurrentUserContext = createContext(null); export default function MyApp() const [theme, setTheme] = useState('light'); const [currentUser, setCurrentUser] = useState(null); return ( ThemeContext.Provider value=theme>> CurrentUserContext.Provider value= <currentUser, setCurrentUser, >> > WelcomePanel /> label> input type="checkbox" checked=theme === 'dark'> onChange=<(e) => setTheme( e.target.checked ? 'dark' : 'light' ); >> /> Use dark mode /label> /CurrentUserContext.Provider> /ThemeContext.Provider> ); > function WelcomePanel( children >) const currentUser > = useContext(CurrentUserContext); return ( Panel title="Welcome"> currentUser !== null ? ( Greeting /> ) : ( LoginForm /> )> /Panel> ); > function Greeting() const currentUser > = useContext(CurrentUserContext); return p>You logged in as currentUser.name>./p>; > function LoginForm() const setCurrentUser > = useContext( CurrentUserContext ); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const canLogin = firstName !== '' && lastName !== ''; return ( <> label> First name': '> input required value=firstName> onChange=<(e) => setFirstName(e.target.value) > /> /label> label> Last name': '> input required value=lastName> onChange=<(e) => setLastName(e.target.value) > /> /label> Button disabled=!canLogin> onClick= => setCurrentUser( name: firstName + ' ' + lastName, >); >> > Log in /Button> !canLogin && i>Fill in both fields./i>> /> ); > function Panel( title, children >) const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( section className=className>> h1>title>/h1> children> /section> ); > function Button( children, disabled, onClick >) const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( button className=className> disabled=disabled> onClick=onClick> > children> /button> ); >
Извлечение провайдеров в компонент¶
По мере роста вашего приложения ожидается, что у вас будет «пирамида» контекстов ближе к корню вашего приложения. В этом нет ничего плохого. Однако, если вам эстетически не нравится вложенность, вы можете извлечь провайдеров в один компонент. В этом примере MyProviders скрывает «сантехнику» и отображает переданные ему дочерние элементы внутри необходимых провайдеров. Обратите внимание, что состояния theme и setTheme нужны в самом MyApp , поэтому MyApp по-прежнему владеет этой частью состояния.
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
import createContext, useContext, useState > from 'react'; const ThemeContext = createContext(null); const CurrentUserContext = createContext(null); export default function MyApp() const [theme, setTheme] = useState('light'); return ( MyProviders theme=theme> setTheme=setTheme>> WelcomePanel /> label> input type="checkbox" checked=theme === 'dark'> onChange=<(e) => setTheme( e.target.checked ? 'dark' : 'light' ); >> /> Use dark mode /label> /MyProviders> ); > function MyProviders( children, theme, setTheme >) const [currentUser, setCurrentUser] = useState(null); return ( ThemeContext.Provider value=theme>> CurrentUserContext.Provider value= <currentUser, setCurrentUser, >> > children> /CurrentUserContext.Provider> /ThemeContext.Provider> ); > function WelcomePanel( children >) const currentUser > = useContext(CurrentUserContext); return ( Panel title="Welcome"> currentUser !== null ? ( Greeting /> ) : ( LoginForm /> )> /Panel> ); > function Greeting() const currentUser > = useContext(CurrentUserContext); return p>You logged in as currentUser.name>./p>; > function LoginForm() const setCurrentUser > = useContext( CurrentUserContext ); const [firstName, setFirstName] = useState(''); const [lastName, setLastName] = useState(''); const canLogin = firstName !== '' && lastName !== ''; return ( <> label> First name': '> input required value=firstName> onChange=<(e) => setFirstName(e.target.value) > /> /label> label> Last name': '> input required value=lastName> onChange=<(e) => setLastName(e.target.value) > /> /label> Button disabled=!canLogin> onClick= => setCurrentUser( name: firstName + ' ' + lastName, >); >> > Log in /Button> !canLogin && i>Fill in both fields./i>> /> ); > function Panel( title, children >) const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( section className=className>> h1>title>/h1> children> /section> ); > function Button( children, disabled, onClick >) const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( button className=className> disabled=disabled> onClick=onClick> > children> /button> ); >
Масштабирование с помощью контекста и редуктора¶
В больших приложениях часто используется сочетание контекста с reducer для извлечения логики, связанной с некоторым состоянием, из компонентов. В этом примере вся «проводка» спрятана в TasksContext.js , который содержит редуктор и два отдельных контекста.
Прочитайте полное прохождение этого примера.
App.js TasksContext.js AddTask.js TaskList.js
1 2 3 4 5 6 7 8 9 10 11 12 13
import AddTask from './AddTask.js'; import TaskList from './TaskList.js'; import TasksProvider > from './TasksContext.js'; export default function TaskApp() return ( TasksProvider> h1>Day off in Kyoto/h1> AddTask /> TaskList /> /TasksProvider> ); >
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
import createContext, useContext, useReducer, > from 'react'; const TasksContext = createContext(null); const TasksDispatchContext = createContext(null); export function TasksProvider( children >) const [tasks, dispatch] = useReducer( tasksReducer, initialTasks ); return ( TasksContext.Provider value=tasks>> TasksDispatchContext.Provider value=dispatch>> children> /TasksDispatchContext.Provider> /TasksContext.Provider> ); > export function useTasks() return useContext(TasksContext); > export function useTasksDispatch() return useContext(TasksDispatchContext); > function tasksReducer(tasks, action) switch (action.type) case 'added': return [ . tasks, id: action.id, text: action.text, done: false, >, ]; > case 'changed': return tasks.map((t) => if (t.id === action.task.id) return action.task; > else return t; > >); > case 'deleted': return tasks.filter((t) => t.id !== action.id); > default: throw Error('Unknown action: ' + action.type); > > > const initialTasks = [ id: 0, text: 'Philosopher’s Path', done: true >, id: 1, text: 'Visit the temple', done: false >, id: 2, text: 'Drink matcha', done: false >, ];
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
import useState, useContext > from 'react'; import useTasksDispatch > from './TasksContext.js'; export default function AddTask() const [text, setText] = useState(''); const dispatch = useTasksDispatch(); return ( <> input placeholder="Add task" value=text> onChange=<(e) => setText(e.target.value)> /> button onClick= => setText(''); dispatch( type: 'added', id: nextId++, text: text, >); >> > Add /button> /> ); > let nextId = 3;
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 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
import useState, useContext > from 'react'; import useTasks, useTasksDispatch, > from './TasksContext.js'; export default function TaskList() const tasks = useTasks(); return ( ul> tasks.map((task) => ( li key=task.id>> Task task=task> /> /li> ))> /ul> ); > function Task( task >) const [isEditing, setIsEditing] = useState(false); const dispatch = useTasksDispatch(); let taskContent; if (isEditing) taskContent = ( <> input value=task.text> onChange=<(e) => dispatch( type: 'changed', task: . task, text: e.target.value, >, >); >> /> button onClick= => setIsEditing(false)>> Save /button> /> ); > else taskContent = ( <> task.text> button onClick= => setIsEditing(true)>> Edit /button> /> ); > return ( label> input type="checkbox" checked=task.done> onChange=<(e) => dispatch( type: 'changed', task: . task, done: e.target.checked, >, >); >> /> taskContent> button onClick= => dispatch( type: 'deleted', id: task.id, >); >> > Delete /button> /label> ); >
Указание запасного значения по умолчанию¶
Если React не может найти поставщиков данного контекста в родительском дереве, значение контекста, возвращаемое useContext() , будет равно значению по умолчанию, которое вы указали при создании контекста:
const ThemeContext = createContext(null);
Значение по умолчанию никогда не изменяется. Если вы хотите обновить контекст, используйте его вместе с state, как описано выше.
Часто вместо null можно использовать какое-то более значимое значение по умолчанию, например:
const ThemeContext = createContext('light');
Таким образом, если вы случайно отобразите какой-либо компонент без соответствующего провайдера, он не сломается. Это также поможет вашим компонентам хорошо работать в тестовой среде без установки большого количества провайдеров в тестах.
В примере ниже кнопка «Toggle theme» всегда светлая, потому что она находится вне любого провайдера контекста темы, а значение контекстной темы по умолчанию — ‘light’ . Попробуйте изменить тему по умолчанию на ‘dark’ .
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 53
import createContext, useContext, useState > from 'react'; const ThemeContext = createContext('light'); export default function MyApp() const [theme, setTheme] = useState('light'); return ( <> ThemeContext.Provider value=theme>> Form /> /ThemeContext.Provider> Button onClick= => setTheme( theme === 'dark' ? 'light' : 'dark' ); >> > Toggle theme /Button> /> ); > function Form( children >) return ( Panel title="Welcome"> Button>Sign up/Button> Button>Log in/Button> /Panel> ); > function Panel( title, children >) const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( section className=className>> h1>title>/h1> children> /section> ); > function Button( children, onClick >) const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( button className=className> onClick=onClick>> children> /button> ); >
Переопределение контекста для части дерева¶
Вы можете переопределить контекст для части дерева, обернув эту часть в провайдер с другим значением.
1 2 3 4 5 6 7
ThemeContext.Provider value="dark"> . ThemeContext.Provider value="light"> Footer /> /ThemeContext.Provider> . /ThemeContext.Provider>
Вы можете вложить и переопределить провайдеров столько раз, сколько вам нужно.
Примеры переопределения контекста¶
1. Переопределение темы¶
Здесь кнопка внутри Footer получает другое значение контекста ( «light» ), чем кнопки снаружи ( «dark» ).
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
import createContext, useContext > from 'react'; const ThemeContext = createContext(null); export default function MyApp() return ( ThemeContext.Provider value="dark"> Form /> /ThemeContext.Provider> ); > function Form() return ( Panel title="Welcome"> Button>Sign up/Button> Button>Log in/Button> ThemeContext.Provider value="light"> Footer /> /ThemeContext.Provider> /Panel> ); > function Footer() return ( footer> Button>Settings/Button> /footer> ); > function Panel( title, children >) const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( section className=className>> title && h1>title>/h1>> children> /section> ); > function Button( children >) const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( button className=className>>children>/button> ); >
2. Автоматически вложенные заголовки¶
Вы можете «накапливать» информацию при вложении провайдеров контекста. В этом примере компонент Section отслеживает LevelContext , который определяет глубину вложенности раздела. Он считывает LevelContext из родительской секции и предоставляет своим дочерним секциям номер LevelContext , увеличенный на единицу. В результате компонент Heading может автоматически решать, какой из тегов , , , . использовать, основываясь на том, сколько компонентов Section вложено в него.
Прочитайте подробное описание этого примера.
App.js Section.js Heading.js LevelContext.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
import Heading from './Heading.js'; import Section from './Section.js'; export default function Page() return ( Section> Heading>Title/Heading> Section> Heading>Heading/Heading> Heading>Heading/Heading> Heading>Heading/Heading> Section> Heading>Sub-heading/Heading> Heading>Sub-heading/Heading> Heading>Sub-heading/Heading> Section> Heading>Sub-sub-heading/Heading> Heading>Sub-sub-heading/Heading> Heading>Sub-sub-heading/Heading> /Section> /Section> /Section> /Section> ); >
1 2 3 4 5 6 7 8 9 10 11 12 13
import useContext > from 'react'; import LevelContext > from './LevelContext.js'; export default function Section( children >) const level = useContext(LevelContext); return ( section className="section"> LevelContext.Provider value=level + 1>> children> /LevelContext.Provider> /section> ); >
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
import useContext > from 'react'; import LevelContext > from './LevelContext.js'; export default function Heading( children >) const level = useContext(LevelContext); switch (level) case 0: throw Error( 'Heading must be inside a Section!' ); case 1: return h1>children>/h1>; case 2: return h2>children>/h2>; case 3: return h3>children>/h3>; case 4: return h4>children>/h4>; case 5: return h5>children>/h5>; case 6: return h6>children>/h6>; default: throw Error('Unknown level: ' + level); > >
1 2 3
import createContext > from 'react'; export const LevelContext = createContext(0);
Оптимизация рендеринга при передаче объектов и функций¶
Вы можете передавать любые значения через контекст, включая объекты и функции.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
function MyApp() const [currentUser, setCurrentUser] = useState(null); function login(response) storeCredentials(response.credentials); setCurrentUser(response.user); > return ( AuthContext.Provider value= <currentUser, login >> > Page /> /AuthContext.Provider> ); >
Здесь контекстное значение — это объект JavaScript с двумя свойствами, одно из которых — функция. Всякий раз, когда MyApp перерисовывается (например, при обновлении маршрута), это будет разный объект, указывающий на разную функцию, поэтому React также должен будет перерисовать все компоненты в глубине дерева, которые вызывают useContext(AuthContext) .
В небольших приложениях это не является проблемой. Однако нет необходимости перерисовывать их, если базовые данные, такие как currentUser , не изменились. Чтобы помочь React воспользоваться этим фактом, вы можете обернуть функцию login в useCallback и обернуть создание объекта в useMemo . Это оптимизация производительности:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
import useCallback, useMemo > from 'react'; function MyApp() const [currentUser, setCurrentUser] = useState(null); const login = useCallback((response) => storeCredentials(response.credentials); setCurrentUser(response.user); >, []); const contextValue = useMemo( () => ( currentUser, login, >), [currentUser, login] ); return ( AuthContext.Provider value=contextValue>> Page /> /AuthContext.Provider> ); >
В результате этого изменения, даже если MyApp потребуется перерендеринг, компонентам, вызывающим useContext(AuthContext) , не потребуется перерендеринг, если только currentUser не изменился.
Устранение неполадок¶
Мой компонент не видит значение от моего провайдера¶
Есть несколько распространенных способов, как это может произойти:
- Вы отображаете в том же компоненте (или ниже), где вы вызываете useContext() . Переместите выше и вне компонента, вызывающего useContext() .
- Возможно, вы забыли обернуть ваш компонент , или вы поместили его в другую часть дерева, чем думали. Проверьте правильность иерархии с помощью React DevTools.
- Возможно, вы столкнулись с какой-то проблемой сборки с вашим инструментарием, из-за которой SomeContext , как видно из предоставляющего компонента, и SomeContext , как видно из читающего компонента, являются двумя разными объектами. Это может произойти, например, если вы используете симлинки. Вы можете проверить это, присвоив им глобальные значения window.SomeContext1 и window.SomeContext2 , а затем проверив в консоли, равно ли window.SomeContext1 === window.SomeContext2 . Если они не одинаковы, исправьте эту проблему на уровне инструмента сборки.
Я всегда получаю undefined из моего контекста, хотя значение по умолчанию другое¶
У вас может быть провайдер без value в дереве:
1 2 3 4
// Doesn't work: no value prop ThemeContext.Provider> Button /> /ThemeContext.Provider>
Если вы забыли указать value , это все равно, что передать value= .
Вы также могли по ошибке использовать другое имя пропса:
1 2 3 4
// Doesn't work: prop should be called "value" ThemeContext.Provider theme=theme>> Button /> /ThemeContext.Provider>
В обоих этих случаях вы должны увидеть предупреждение от React в консоли. Чтобы исправить их, вызовите свойство value :
1 2 3 4
// ✅ Passing the value prop ThemeContext.Provider value=theme>> Button /> /ThemeContext.Provider>
Обратите внимание, что значение по умолчанию из вашего вызова createContext(defaultValue) используется только если выше вообще нет подходящего провайдера. Если где-то в родительском дереве есть компонент > , компонент, вызывающий useContext(SomeContext) получит undefined в качестве значения контекста.
Ссылки¶
Учим useContext на примерах — React Hooks
Доброго времени суток, друзья. Сегодня поговорим о контексте в React и работе с хуком useContext. Разберем контекст в React и какую проблему он решает. На примере передачи данных между двумя независимыми компонентами посмотрим на практическую сторону использования useContext.
Видео на эту тему.
Что такое контекст?
онтекст в React это способность передачи данных через дерево компонентов, минуя прокидывание данных через пропсы от одного компонента к другому. Если посмотреть на историю развития React, то мы увидим, что до создания Flux, Reflux, Redux и Mobx решений все данные передавались от самого верхнего компонента к нижним через пропсы, иногда минуя огромные цепочки связанных компонентов. Это было крайне неудобное решение, если приложение насчитывало сотни, а иногда даже и тысячи связанных компонентов. Чтобы решить данною проблему, разработчики React придумали Context API, благодаря которому процесс передачи стал гораздо проще и лаконичнее.
Для создания контекста достаточно воспользоваться методом React.createContext, он имеет единственный параметр, в который можно передать дефолтное значение или объект для получения дочерних компонентов через контекст.
const context = React.createContext('value');
Вызывая React.createContext, мы получаем объект, который содержит два компонента, позволяющие взаимодействовать с ним.
Компонент Provider
После создания контекста, его требуется использовать совместно с компонентом Provider, который позволяет дочерним компонентам подписаться на его изменения. Если проще, то благодаря компоненту Provider все дочерние компоненты могут получить значения, которые мы ему передаем.
Данный компонент имеет пропс value, в котором можно указать значение для передачи всем дочерним компонентам.
Компонент Consumer
Второй компонент, который находится в объекте после вызова React.createContext является Consumer. Он предназначен для подписи на изменение контекста в функциональном компоненте. В качестве дочернего компонента он принимает функцию, которая получает контекст и возвращает React компонент. Благодаря компоненту Consumer у нас есть возможность получать значения из контекста в компонентах.
< value = >value >
Несколько контекстов
Часто одного контекста в приложении бывает не достаточно и требуется использовать несколько контекстов. Для этого как правило просто оборачивают один Provider другим и передают в них нужные значения.
Тоже самое делают и с Consumer для получения значений в дочерних компонентах.
Получение контекста в Class компонентах
Для получения контекста в Class компоненте необходимо использовать свойство contextType, имеющееся в самом классе, и присвоить объект, возвращающий контекст. После чего, используя свойства this.context, можно получить доступ к его значениям уже внутри класса.
class ComponentOne extends React.Component < componentDidMount() < const value = this.context; >componentDidUpdate() < const value = this.context; >render() < const value = this.context; >> ComponentOne.contextType = Context;
Для чего нужен useContext?
После изучения всей базовой теории по контексту можно наконец-то перейти к хукам. Основная задача хуков в React это упрощение API с целью написания меньшего количества кода и удобства. Хук useContext занимается именно тем, что уменьшает количество написанного кода для взаимодействия с контекстом в функциональных компонентах.
const value = useContext(Context)
Хук имеет один параметр, в который требуется передать объект контекста, получаемый при вызове React.createContext.
Важно! При изменении значений в контексте, компонент, который содержит useContext, всегда будет перерендериваться.
useContext на практике
Давайте создадим компонент, в котором у нас будет храниться контекст и два дочерних компонента. При нажатии на кнопку в одном компоненте, будет происходить изменение текста в другом. Данный пример поможет лучше понять, как работать с контекстом, используя useContext.
import React, < useState >from "react"; import < Context >from "./Context.js"; import ComponentA from "./ComponentA"; import ComponentB from "./ComponentB"; export default function App() < const [context, setContext] = useState("default context value"); return ( > ); >
import React from "react"; export const Context = React.createContext();
import React, < useContext >from "react"; import < Context >from "./Context"; export default function ComponentA() < const [context, setContext] = useContext(Context); return ( ComponentA: ); >
import React, < useContext >from "react"; import < Context >from "./Context"; export default function ComponentB() < const [context, setContext] = useContext(Context); return ComponentB: ; >
Давайте разберем по шагам, что происходит в этом примере. В Context.js создается сам контекст, а в App.js импортируются два компонента и контекст, которым они оборачиваются, c передачей в value начального состояния (context) и функции для его изменения (setContext).
Два дочерних компонента импортируют в себя контекст, используя хук useContext и посредством деструктуризации вытаскивается значение и функция для ее изменения.
При нажатии на кнопку в компоненте ComponentA происходит изменение значения контекста, благодаря useContext в компоненте ComponentB происходит его моментальное перерендеривание и, как результат в нем отображается значение контекста.
Заключение
Сегодня мы разобрали понятие контекста в React и на примерах посмотрели практическое применение useContext. Благодаря использованию useContext работа с контекстом стала более лаконичной и простой. Помните, использование контекста в вашем приложении может усложнить повторное использование компонентов, и в качестве альтернативы вы можете использовать более простое и эффективное решение, а именно композицию компонентов. Надеюсь, что данная статья была вам полезна. Учитесь, думайте, пишите код. Удачного кодинга, друзья!
Подписывайтесь на наш канал в Telegram и на YouTube для получения самой последней и актуальной информации.
Хук useContext — как использовать контекст в React?
useContext — это React хук, который позволяет вам читать и подписываться на контекст из вашего компонента.
API хука useContext
const value = useContext(SomeContext)
useContext(SomeContext)
Вызовите useContext на верхнем уровне вашего компонента для чтения и подписки на контекст.
import < useContext >from 'react'; function MyComponent() < const theme = useContext(ThemeContext); // .
Параметры
- SomeContext : Контекст, который вы ранее создали с помощью createContext . Сам контекст не содержит информации, он только представляет тип информации, которую вы можете предоставить или прочитать из компонентов.
Что возвращает useContext ?
useContext возвращает значение контекста для вызывающего компонента. Оно определяется как значение, переданное ближайшему SomeContext.Provider , расположенному выше вызывающего компонента в дереве. Если такого провайдера нет, то возвращаемое значение будет значением по умолчанию ( defaultValue ), которое вы передали в createContext для данного контекста. React автоматически перерисовывает компоненты, которые читают некоторый контекст, если он изменяется.
Использование контекста
Передача данных вглубь дерева
Вызовите useContext на верхнем уровне вашего компонента для чтения и подписки на контекст.
import < useContext >from 'react'; function Button() < const theme = useContext(ThemeContext); // .
useContext возвращает значение контекста для переданного вами контекста. Чтобы определить значение контекста, React просматривает дерево компонентов и находит ближайший вышеуказанный провайдер контекста для данного контекста. Чтобы передать контекст кнопке, оберните ее или один из ее родительских компонентов в соответствующий провайдер контекста:
function MyPage() < return (
); > function Form() < // . отрисовывает кнопки внутри себя . > Не имеет значения, сколько слоев компонентов находится между провайдером и кнопкой. Когда кнопка в любом месте формы вызывает useContext(ThemeContext) , она получит значение "dark" .
import < createContext, useContext >from 'react'; const ThemeContext = createContext(null); export default function MyApp() < return (
) > function Form() < return ( ); > function Panel(< title, children >) < const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( > ) > function Button(< children >) < const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( > ); > Обновление данных, переданных через контекст
Часто бывает необходимо, чтобы контекст менялся с течением времени. Чтобы обновить контекст, вам нужно объединить его с состоянием. Объявите переменную state в родительском компоненте и передайте текущее состояние в качестве значения контекста провайдеру.
unction MyPage() < const [theme, setTheme] = useState('dark'); return (
> ); >Теперь любая кнопка внутри провайдера будет получать текущее значение темы. Если вы вызовете setTheme для обновления значения темы, которое вы передаете провайдеру, все компоненты Button будут заново отображаться с новым значением "light" .
Указание значения по умолчанию
Если React не может найти ни одного провайдера данного контекста в родительском дереве, значение контекста, возвращаемое функцией useContext() , будет равно значению по умолчанию, которое вы указали при создании контекста:
const ThemeContext = createContext(null);
Значение по умолчанию никогда не изменяется. Если вы хотите обновить контекст, используйте его вместе с состоянием, как описано выше. Часто вместо null можно использовать какое-то более значимое значение по умолчанию, например:
const ThemeContext = createContext('light');
Таким образом, если вы случайно отобразите какой-то компонент без соответствующего провайдера, он не сломается. Это также поможет вашим компонентам хорошо работать в тестовой среде без установки большого количества провайдеров в тестах. В приведенном ниже примере кнопка "Toggle theme" всегда светлая, потому что она находится вне любого провайдера контекста темы, а значение контекстной темы по умолчанию - 'light' .
import < createContext, useContext, useState >from 'react'; const ThemeContext = createContext('light'); export default function MyApp() < const [theme, setTheme] = useState('light'); return ( <>
> >) > function Form(< children >) < return (); > function Panel(< title, children >) < const theme = useContext(ThemeContext); const className = 'panel-' + theme; return ( > ) > function Button(< children, onClick >) < const theme = useContext(ThemeContext); const className = 'button-' + theme; return ( onClick=> ); > Переопределение контекста для части дерева
Вы можете переопределить контекст для части дерева, обернув эту часть в провайдер с другим значением.
Вы можете вложить и переопределить провайдеров столько раз, сколько вам нужно.
Оптимизация повторных рендерингов при передаче объектов и функций
Вы можете передавать любые значения через контекст, включая объекты и функции.
function MyApp() < const [currentUser, setCurrentUser] = useState(null); function login(response) < storeCredentials(response.credentials); setCurrentUser(response.user); >return (
>> ); >Здесь значение контекста - это JavaScript объект с двумя свойствами, одно из которых - функция. Всякий раз, когда MyApp ререндерится (например, при обновлении маршрута), это будет другой объект, указывающий на другую функцию, поэтому React также придется перерендерить все компоненты в глубине дерева, которые вызывают useContext(AuthContext) . В небольших приложениях это не является проблемой. Однако нет необходимости перерисовывать их, если базовые данные, такие как currentUser , не изменились. Чтобы помочь React воспользоваться этим фактом, вы можете обернуть функцию входа в систему в useCallback и обернуть создание объекта в useMemo .
Это оптимизация производительности будет выглядеть следующим образом:
import < useCallback, useMemo >from 'react'; function MyApp() < const [currentUser, setCurrentUser] = useState(null); const login = useCallback((response) =>< storeCredentials(response.credentials); setCurrentUser(response.user); >, []); const contextValue = useMemo(() => (< currentUser, login >), [currentUser, login]); return (
> ); >В результате этого изменения, даже если MyApp потребуется повторный рендеринг, компонентам, вызывающим useContext(AuthContext) , не потребуется повторный рендеринг, если только currentUser не изменился.
Как вызвать метод дочернего компонента из родительского компонента с помощью useImperativeHandle
9 месяцев назад · 3 мин. на чтение
Быстрый старт с useImperativeHandle
В этой статье будет показано, как вызвать метод дочернего компонента с помощью ссылки. Чтобы решить эту проблему, мы будем использовать хуки useRef и useImperativeHandle .
Дочерний компонент
Начнем с простого дочернего компонента, в котором содержится кнопка. Нажатие на кнопку вызывает внутренний метод doSomething .
// Child.jsx function Child(props, ref) < const doSomething = () =>< console.log("do something"); >; return (
); > export default Child;Child Component
Родительский компонент
Далее рассмотрим родительский компонент. В нем используется дочерний компонент, описанный выше. Обратите внимание, что в родительском компоненте есть собственная кнопка сохранения.
// App.jsx import Child from "./Child"; function App() < const save = () =><>; return (
); > export default App;Хук useImperativeHandle
Теперь давайте сосредоточимся на нашей задаче. Мы хотим вызвать метод ( doSomething ) дочернего компонента при нажатии кнопки ( Save ) из родительского компонента. Чтобы вызвать метод из дочернего компонента, нам нужно сначала выставить его наружу. useImperativeHandle определяет значение объекта, которое предоставляется родительскому компоненту при использовании ref . Добавляя наш метод к этому объекту, мы делаем его доступным в родительских компонентах.
// Child.jsx import < useImperativeHandle >from "react"; function Child(props, ref) < const doSomething = () =>< console.log("do something"); >; useImperativeHandle(ref, () => (< doSomething >)); return (
); > export default Child;Child Component
useImperativeHandle следует использовать с forwardRef .
forwardRef позволяет родительскому компоненту передавать ссылки своим дочерним элементам. Чтобы прикрепить функции или поля к этой ссылке (к рефу), используется хук useImperativeHandle .
// Child.jsx import < forwardRef, useImperativeHandle >from "react"; function Child(props, ref) < const doSomething = () =>< console.log("do something"); >; useImperativeHandle(ref, () => (< doSomething >)); return (
); > export default forwardRef(Child); // Child обернут в forwardRefChild Component
На этом этапе мы можем создать ссылку в родительском компоненте с помощью хука useRef и передать ее дочернему компоненту. Получив эту ссылку, мы можем вызвать метод doSomething дочернего компонента.
// App.jsx import < useRef >from "react"; import Child from "./Child"; function App() < const childRef = useRef(null); const save = () => < if (childRef.current) < childRef.current.doSomething(); >>; return (
/>); > export default App;Добавим TypeScript
Далее посмотрим, какие изменения нужно сделать, чтобы вызвать тот же дочерний метод из родительского компонента при использовании TypeScript. Во-первых, нам нужно определить новый интерфейс, содержащий метод, который будет представлен.
export interface RefType < doSomething: () =>void; >
Затем новый тип ( RefType ) используется при получении ссылки в дочернем компоненте.
function Child(props: PropsType, ref: Ref)
Ниже приведен полный код дочернего компонента.
// Child.jsx import < forwardRef, useImperativeHandle, Ref >from "react"; export interface PropsType <> export interface RefType < doSomething: () =>void; > function Child(props: PropsType, ref: Ref) < const doSomething = () =>< console.log("do something"); >; useImperativeHandle(ref, () => (< doSomething >)); return (
); > export default forwardRef(Child);Child Component
В родительский компонент нам нужно импортировать этот RefType , содержащий все публичные дочерние методы, и использовать его при создании ref .
// App.jsx import Child, < RefType >from "./Child"; //. const childRef = useRef(null);
Полный код родительского компонента.
import < useRef >from "react"; import Child, < RefType >from "./Child"; function App() < const childRef = useRef(null); const save = () => < if (childRef.current) < childRef.current.doSomething(); >>; return (
/>); > export default App;