JavaRush /Курсы /Модуль 3: React /Роль контекста в управлении состоянием приложения

Роль контекста в управлении состоянием приложения

Модуль 3: React
2 уровень , 8 лекция
Открыта

Зачем нам нужен контекст?

Проблема "Проп-дриллинга" (Prop Drilling)

Одна из самых распространённых проблем в React-приложениях — это "проп-дриллинг". Представьте, что у вас есть родительский компонент, который должен передать данные (например, имя пользователя и его роль) далёкому потомку через несколько уровней вложенности. И для этого все промежуточные компоненты вынуждены принимать данные через props, даже если сами они их не используют.

Вот пример:

const Parent = () => {
  const user = { name: "Alice", role: "Admin" };

  return ChildA user={user} />;
};

const ChildA = ({ user }: { user: { name: string; role: string } }) => {
  return <ChildB user={user} />;
};

const ChildB = ({ user }: { user: { name: string; role: string } }) => {
  return <p>User: {user.name}, Role: {user.role}</p>;
};

На первый взгляд, ничего страшного. Но что, если в будущем вам придётся обрабатывать ещё больше данных через десятки вложенных компонентов? Каждый из них станет вынужденным "перевалочным пунктом". Это делает код громоздким, сложным в поддержке и увеличивает вероятность ошибок.

Контекст как решение проблемы

Контекст позволяет "вытянуть" данные на уровень, который доступен для всех компонентов дерева, исключая необходимость прокидывания через всё дерево. Воспользовавшись контекстом, мы можем переписать пример так:

import React, { createContext, useContext } from "react";

const UserContext = createContext<{ name: string; role: string } | undefined>(undefined);

const Parent = () => {
  const user = { name: "Alice", role: "Admin" };

  return (
    <UserContext.Provider value={user}>
      <ChildA />
    </UserContext.Provider>
  );
};

const ChildA = () => {
  return <ChildB />;
};

const ChildB = () => {
  const user = useContext(UserContext);

  if (!user) {
    return <p>No user available</p>;
  }

  return <p>User: {user.name}, Role: {user.role}</p>;
};

Теперь мы можем напрямую получить доступ к данным с помощью хука useContext, не беспокоя промежуточные компоненты. Удобно, правда?

Роль контекста в сложных приложениях

Контекст прекрасно подходит для управления состоянием или данными, которые нужно глобально распространять по приложению. Это могут быть:

  1. Тема интерфейса (светлая/тёмная).
  2. Данные текущего пользователя (имя, роль, токен).
  3. Настройки локализации (язык, валюта).
  4. Корзина покупок в интернет-магазине.
  5. Глобальное состояние приложения (например, с использованием useReducer).

Контекст — это своего рода "единый источник истины" для данных, которые должны быть доступны на любом уровне дерева компонентов.

Пример: управление темой интерфейса с помощью контекста

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

1. Создаём контекст

import React, { createContext, useContext, useState, ReactNode } from "react";

// Типы данных для темы
type Theme = "light" | "dark";

// Интерфейс контекста
interface ThemeContextType {
  theme: Theme;
  toggleTheme: () => void;
}

// Создаём контекст
const ThemeContext = createContext<ThemeContextType | undefined>(undefined);

2. Создаём провайдер

Мы определяем компонент-провайдер, который будет управлять состоянием темы и предоставлять функции переключения.

interface ThemeProviderProps {
  children: ReactNode;
}

const ThemeProvider = ({ children }: ThemeProviderProps) => {
  const [theme, setTheme] = useState<Theme>("light");

  const toggleTheme = () => {
    setTheme((prevTheme) => (prevTheme === "light" ? "dark" : "light"));
  };

  return (
    <ThemeContext.Provider value={{ theme, toggleTheme }}>
      {children}
    </ThemeContext.Provider>
  );
};

3. Используем контекст

Теперь мы можем легко получить доступ к теме и функции переключения с помощью useContext.

const Header = () => {
  const themeContext = useContext(ThemeContext);

  if (!themeContext) {
    throw new Error("Header must be used within ThemeProvider");
  }

  const { theme, toggleTheme } = themeContext;

  return (
    <header
            style={{
            background: theme === "light" ? "#fff" : "#333",
        color: theme === "light" ? "#000" : "#fff",
        padding: "10px",
      }}
    >
      <h1>Current Theme: {theme}</h1>
      <button onClick={toggleTheme}>Toggle Theme</button>
    </header>
    );
};

4. Встраиваем в приложение

const App = () => {
  return (
    <ThemeProvider>
      <Header />
    </ThemeProvider>
  );
};

export default App;

Теперь наша тема глобальна и доступна любому компоненту внутри ThemeProvider!

Преимущества использования контекста

  1. Избавление от проп-дриллинга: контекст решает проблему ручного прокидывания данных через множество уровней компонентов.
  2. Централизованное управление состоянием: данные и функции, предоставленные контекстом, всегда находятся в одном месте.
  3. Простота управления: контекст легко обновляется и управляется, особенно в связке с useReducer.
  4. Типизация с TypeScript: с контекстом мы можем определить строгие интерфейсы, устранив ошибки типов на этапе компиляции.

Когда НЕ использовать контекст?

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

В таких случаях можно рассмотреть альтернативы, например:

  • Использование Redux для глобального состояния.
  • Распределение данных по нескольким контекстам для разделения ответственности.
  • Локальное состояние useState для данных, которые не требуют глобального доступа.

Практические кейсы использования контекста

  1. Авторизация пользователя:

    • Сохраняйте токен и данные пользователя в контексте, чтобы легко управлять доступом к защищённым маршрутам.
  2. Настройки локализации:

    • Храните язык приложения и переключайте его с помощью контекста.
  3. Состояние корзины покупок:

    • Управляйте добавлением/удалением продуктов в корзине через глобальный контекст.
  4. Глобальные уведомления (toast):

    • Создайте контекст для отображения уведомлений в любом месте приложения.

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

1
Задача
Модуль 3: React, 2 уровень, 8 лекция
Недоступна
Создание и использование контекста
Создание и использование контекста
1
Задача
Модуль 3: React, 2 уровень, 8 лекция
Недоступна
Управление глобальной темой
Управление глобальной темой
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ