useContext
useContext
це хук React, який дозволяє вам читати та підписуватися на контекст з вашого компонента.
const value = useContext(SomeContext)
Посилання
useContext(SomeContext)
Викличте useContext
на верхньому рівні вашого компонента, щоб прочитати та підписатися на context.
import { useContext } from 'react';
function MyComponent() {
const theme = useContext(ThemeContext);
// ...
Дивіться більше прикладів нижче.
Параметри
SomeContext
: Контекст, який ви раніше створили за допомогоюcreateContext
. Сам контекст не містить інформації, він лише представляє тип інформації, яку ви можете надати або прочитати з компонентів.
Повернення
useContext
повертає значення контексту для викликаючого компонента. Воно визначається як значення
, передане найближчому SomeContext.Provider
над викликаючим компонентом у дереві. Якщо такого провайдера немає, то поверненим значенням буде defaultValue
, яке ви передали до createContext
для цього контексту. Значення, що повертається, завжди актуальне. React автоматично повторно ерендерить компоненти, які читають деякий контекст, якщо він змінюється.
Застереження
useContext()
на виклик компонента не впливають провайдери, повернуті з того ж самого компонента. Відповідний<Context.Provider>
має бути над компонентом, що виконуєuseContext()
виклик.- React автоматично перерендерить всі дочірні елементи, які використовують певний контекст, починаючи з постачальника, який отримує інше
значення
. Попереднє і наступне значення порівнюються за допомогою порівнянняObject.is
. Пропуск повторного рендерингу зmemo
не заважає дочірнім елементам отримувати свіжі значення контексту. - Якщо ваша система збирання створює дублікати модулів на виході (що може статися з символьними посиланнями), це може порушити контекст. Передача чогось через контекст працює лише у випадку, якщо
SomeContext
, який ви використовуєте для надання контексту, іSomeContext
, який ви використовуєте для його зчитування, є точно тим самим об'єктом, як визначено за допомогою===
порівняння.
Використання
Передача даних вглиб дерева
Викличте useContext
на верхньому рівні вашого компонента, щоб прочитати та підписатися на context.
import { useContext } from 'react';
function Button() {
const theme = useContext(ThemeContext);
// ...
useContext
повертає
function MyPage() {
return (
<ThemeContext.Provider value="dark">
<Form />
</ThemeContext.Provider>
);
}
function Form() {
// ... renders buttons inside ...
}
Не має значення, скільки шарів компонентів знаходиться між постачальником та Button
. Коли Button
в будь-якому місці всередині Form
викликає useContext(ThemeContext)
, він отримає "dark"
як значення.
useContext()
завжди шукає найближчого постачальника над компонентом, який його викликає. Він шукає вгору і не враховує провайдерів у компоненті, з якого ви викликаєте useContext()
.
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>
);
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}
Оновлення даних, переданих через контекст
Часто вам потрібно, щоб контекст змінювався з часом. Щоб оновити контекст, об'єднайте його зі станом. Оголосіть змінну стану у батьківському компоненті і передайте поточний стан вниз як значення Тепер будь-який Button
всередині провайдера отримає поточне значення theme
. Якщо ви викличете setTheme
для оновлення значення theme
, яке ви передали провайдеру, усі компоненти Button
буде повторно відрендерено з новим значенням 'light'
.
Оновлення значення через контекст
У цьому прикладі компонент MyApp
містить змінну стану, яка потім передається провайдеру ThemeContext
. Встановлення прапорця "Темний режим" оновлює стан. Зміна наданого значення повторно рендерить усі компоненти, що використовують цей контекст.
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>
);
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}
Зверніть увагу, що value="dark"
передає рядок "dark"
, а value={theme}
передає значення змінної JavaScript theme
з JSX фігурними дужками. Фігурні дужки також дозволяють передавати контекстні значення, які не є рядками.
Оновлення об'єкта через контекст
У цьому прикладі є змінна стану currentUser
, яка містить об'єкт. Ви об'єднуєте { currentUser, setCurrentUser }
в один об'єкт і передаєте його вниз через контекст всередині value={}
. Це дозволяє будь-якому компоненту нижче, наприклад, LoginButton
, читати як currentUser
, так і setCurrentUser
, а потім викликати setCurrentUser
за потреби.
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>
);
}
label {
display: block;
}
.panel {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.button {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
Кілька контекстів
У цьому прикладі є два незалежні контексти. ThemeContext
надає поточну тему, яка є рядком, тоді як CurrentUserContext
містить об'єкт, що представляє поточного користувача.
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.trim() !== '' && lastName.trim() !== '';
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>
);
}
label {
display: block;
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}
Витяг провайдерів до компонента
З ростом вашої програми очікується, що ви матимете "піраміду" контекстів ближче до кореня вашої програми. У цьому немає нічого поганого. Однак, якщо вам не подобається вкладеність з естетичної точки зору, ви можете витягти провайдерів в один компонент. У цьому прикладі MyProviders
приховує "бруд" і рендерить передані йому дочірні компоненти всередині необхідних провайдерів. Зауважте, що стан теми
та setTheme
потрібен самому MyApp
, тому MyApp
досі володіє цією частиною стану.
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>
);
}
label {
display: block;
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}
Масштабування за допомогою редуктора та контексту
У більших застосунках зазвичай комбінують контекст з редуктором, щоб витягти з компонентів логіку, пов'язану з певним станом. У цьому прикладі вся "проводка" схована у TasksContext.js
, який містить редуктор та два окремі контексти.
Прочитайте повний опис цього прикладу.
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>
);
}
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 }
];
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;
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>
);
}
button { margin: 5px; }
li { list-style-type: none; }
ul, li { margin: 0; padding: 0; }
Вказівка запасного значення за замовчуванням
Якщо React не може знайти жодного постачальника цього конкретного Значення за замовчуванням не змінюється. Якщо ви хочете оновити контекст, використовуйте його зі станом як описано вище. Нерідко замість Таким чином, якщо ви випадково відрендерите якийсь компонент без відповідного провайдера, він не зламається. Це також допоможе вашим компонентам добре працювати у тестовому середовищі без встановлення великої кількості провайдерів у тестах. У наведеному нижче прикладі кнопка "Перемкнути тему" завжди підсвічена, оскільки вона поза межами будь-якого постачальника контексту теми, а значенням типової теми контексту є null
використовується якесь більш значуще значення, яке можна використовувати за замовчуванням, наприклад:const ThemeContext = createContext('light');
'light'
. Спробуйте змінити тему за замовчуванням на 'dark'
.
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>
);
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
margin-bottom: 10px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}
Перевизначення контексту для частини дерева
Ви можете перевизначити контекст для частини дерева, обгорнувши цю частину у провайдер з іншим значенням.
<ThemeContext.Provider value="dark">
...
<ThemeContext.Provider value="light">
<Footer />
</ThemeContext.Provider>
...
</ThemeContext.Provider>
Ви можете вкладати та перевизначати провайдери стільки разів, скільки вам потрібно.
Перевизначення теми
Тут кнопка всередині кнопки Footer
отримує інше значення контексту ("light"
), ніж кнопки зовні ("dark"
).
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>
);
}
footer {
margin-top: 20px;
border-top: 1px solid #aaa;
}
.panel-light,
.panel-dark {
border: 1px solid black;
border-radius: 4px;
padding: 20px;
}
.panel-light {
color: #222;
background: #fff;
}
.panel-dark {
color: #fff;
background: rgb(23, 32, 42);
}
.button-light,
.button-dark {
border: 1px solid #777;
padding: 5px;
margin-right: 10px;
margin-top: 10px;
}
.button-dark {
background: #222;
color: #fff;
}
.button-light {
background: #fff;
color: #222;
}
Автоматично вкладені заголовки
Ви можете "накопичувати" інформацію при вкладанні контекстних провайдерів. У цьому прикладі компонент Section
відстежує LevelContext
, який визначає глибину вкладеності секцій. Він зчитує LevelContext
з батьківської секції і надає дочірнім секціям число LevelContext
, збільшене на одиницю. У результаті компонент Heading
може автоматично вирішувати, який з тегів <h1>
, <h2>
, <h3>
, ..., використовувати, залежно від кількості компонентів Section
, у які він вкладений.
Прочитайте детальний опис цього прикладу.
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>
);
}
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>
);
}
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);
}
}
import { createContext } from 'react';
export const LevelContext = createContext(0);
.section {
padding: 10px;
margin: 5px;
border-radius: 5px;
border: 1px solid #aaa;
}
Оптимізація повторного рендерингу при передачі об'єктів та функцій
Через контекст можна передавати будь-які значення, включаючи об'єкти та функції.
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>
);
}
Тут контекстне значення useContext(AuthContext)
.
У невеликих програмах це не є проблемою. Однак, немає необхідності повторно рендерити їх, якщо базові дані, такі як currentUser
, не змінилися. Щоб допомогти React скористатися цим фактом, ви можете обгорнути функцію login
у 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 (
<AuthContext.Provider value={contextValue}>
<Page />
</AuthContext.Provider>
);
}
У результаті цієї зміни, навіть якщо MyApp
потребує переобчислення, компоненти, що викликають useContext(AuthContext)
, не потребуватимуть переобчислення, якщо не змінився currentUser
.
Додатково про useMemo
та useCallback
.
Налагодження
Мій компонент не бачить значення від мого провайдера
Існує декілька поширених способів, як це може статися:
- Ви відображаєте
<SomeContext.Provider>
у тому ж компоненті (або нижче), де ви викликаєтеuseContext()
. Перемістіть<SomeContext.Provider>
вище і за межі компонента, що викликаєuseContext()
. - Можливо, ви забули обгорнути ваш компонент за допомогою
<SomeContext.Provider>
, або помістили його в іншу частину дерева, ніж ви думали. Перевірте правильність ієрархії за допомогою React DevTools. - Ви можете зіткнутися з проблемою збірки вашого інструментарію, яка призводить до того, що
SomeContext
, як його бачить компонент, що надає, іSomeContext
, як його бачить компонент, що читає, є двома різними об'єктами. Це може статися, наприклад, якщо ви використовуєте символічні посилання. Ви можете перевірити це, призначивши їх глобалам на кшталтwindow.SomeContext1
іwindow.SomeContext2
, а потім перевірити, чи єwindow.SomeContext1 === window.SomeContext2
у консолі. Якщо вони не збігаються, виправте цю проблему на рівні інструмента збірки.
Я завжди отримую undefined
з мого контексту, хоча значення за замовчуванням інше
У вас може бути провайдер без значення
у дереві:
// 🚩 Doesn't work: no value prop
<ThemeContext.Provider>
<Button />
</ThemeContext.Provider>
Якщо ви забудете вказати значення
, це буде схоже на передачу value={undefined}
.
Ви також могли помилково використати іншу назву пропса:
// 🚩 Doesn't work: prop should be called "value"
<ThemeContext.Provider theme={theme}>
<Button />
</ThemeContext.Provider>
В обох цих випадках ви повинні побачити попередження від React в консолі. Щоб виправити їх, викличте проп value
:
// ✅ Passing the value prop
<ThemeContext.Provider value={theme}>
<Button />
</ThemeContext.Provider>
Зауважте, що значення за замовчуванням з вашого createContext(defaultValue)
виклику використовується лише якщо вище не знайдено відповідного провайдера взагалі. Якщо десь у батьківському дереві є компонент <SomeContext.Provider value={undefined}>
, то компонент, який викликає useContext(SomeContext)
, отримає як контекстне значення невизначене
значення