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 повертає контекстне значення </CodeStep> для <CodeStep data-step="1">контекст</CodeStep> ви пройшли. Щоб визначити значення контексту, React шукає в дереві компонентів і знаходить <strong>найближчого постачальника контексту вище</strong> для цього конкретного контексту.</p> <p>Щоб передати контекст до <коду>Button, обгорніть його або один з його батьківських компонентів у відповідний провайдер контексту:

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;
}

Оновлення даних, переданих через контекст

Часто вам потрібно, щоб контекст змінювався з часом. Щоб оновити контекст, об'єднайте його зі станом. Оголосіть змінну стану у батьківському компоненті і передайте поточний стан вниз як значення контексту</CodeStep> до провайдера.</p> <pre><code data-meta="{2} [[1, 4, "ThemeContext"], [2, 4, "theme"], [1, 11, "ThemeContext"]]" class="language-js">function MyPage() { const [theme, setTheme] = useState('dark'); return ( <ThemeContext.Provider value={theme}> <Форма /> <Button onClick={() => { setTheme('light'); }}> Перехід на світлу тему </Кнопка> </ThemeContext.Provider> ); }

Тепер будь-який 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 не може знайти жодного постачальника цього конкретного контексту</CodeStep> у батьківському дереві значення контексту, що повертається функцією <code>useContext() буде дорівнювати значенню за замовчуванням /CodeStep> що ви вказали, коли <a href="/reference/react/createContext">створювали цей контекст</a>:</p> <pre><code data-meta="[[1, 1, "ThemeContext"], [3, 1, "null"]]" class="language-js">const ThemeContext = createContext(null);

Значення за замовчуванням не змінюється. Якщо ви хочете оновити контекст, використовуйте його зі станом як описано вище.

Нерідко замість 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>
  );
}

Тут контекстне значення </CodeStep> - це JavaScript-об'єкт з двома властивостями, одна з яких є функцією. Щоразу, коли <код>MyApp повторно рендериться (наприклад, при оновленні маршруту), це буде об'єкт different, що вказує на функцію different, тому React також повинен буде повторно рендерити всі компоненти в глибині дерева, які викликають 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.


Налагодження

Мій компонент не бачить значення від мого провайдера

Існує декілька поширених способів, як це може статися:

  1. Ви відображаєте <SomeContext.Provider> у тому ж компоненті (або нижче), де ви викликаєте useContext(). Перемістіть <SomeContext.Provider> вище і за межі компонента, що викликає useContext().
  2. Можливо, ви забули обгорнути ваш компонент за допомогою <SomeContext.Provider>, або помістили його в іншу частину дерева, ніж ви думали. Перевірте правильність ієрархії за допомогою React DevTools.
  3. Ви можете зіткнутися з проблемою збірки вашого інструментарію, яка призводить до того, що 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) , отримає як контекстне значення невизначене значення