React Context API

Context API — это современный способ управления состоянием в React. Предназначен для совместного использования части данных между многими компонентами React без детализации. В этой статье мы разберем недостатки контекстного API, фабрику логических оберток и действий.

В этой статье мы рассмотрим Context API, начиная с понимания его необходимости в приложениях React, до его настройки и эффективного использования. Мы также рассмотрим распространенные варианты использования, обсудим лучшие практики, чтобы вы могли использовать Context API в полной мере.

Понимание необходимости контекста в React

Давайте рассмотрим простой пример, в котором у нас есть , ParentComponent содержащий некоторое count состояние, и вам нужно передать это состояние в глубоко вложенный GrandchildComponent.

Вот , ParentComponent который хранит состояние, setState но не использует их:

'use client';

import { useState } from 'react';
import ChildComponent from './ChildComponent';

const ParentComponent = () => {
 const [count, setCount] = useState(0);

 return (
   <>
     <div className="text-center mt-3">
       <h2 className="text-3xl">Parent Component</h2>
       <small>Not using the count state</small>
     </div>


     <ChildComponent count={count} setCount={setCount} />
   </>
 );
};

export default ParentComponent;

ChildComponent не использует state и setState тоже, но все равно должно брать их у ParentComponent и передавать GrandChildComponent кто в них нуждается:

'use client';

import GrandChildComponent from './GrandChildComponent';

const ChildComponent = ({ count, setCount }) => {
 return (
   <>
     <div className="text-center mt-3">
       <h2 className="text-3xl">Child Component</h2>
       <small>Not Using the count state too</small>
     </div>

     <GrandChildComponent count={count} setCount={setCount} />
   </>
 );
};

export default ChildComponent;

GrandChildComponent нуждается в состоянии и setState, и использует их:

'use client';

const GrandChildComponent = ({ count, setCount }) => {
 return (
   <div>
     <div className="text-center mt-3">
       <h2 className="text-3xl">Grandchild Component</h2>
       <small>Using the count state</small>
     </div>
     <div className="text-center">
       <h3 className="text-2xl">Count is: {count}</h3>
       <button
         onClick={() => setCount(count + 1)}
         className="bg-pink-600 p-2 rounded text-white"
       >
         Increase Count
       </button>
     </div>
   </div>
 );
};

export default GrandChildComponent;

А вот как все выглядит в браузере после ParentComponent импорта в Home компонент проекта Next JS:

Изображение

Это prop drilling в действии. Вы можете видеть , ChildComponentкоторое не использует count состояние и setCountвсе еще должно поглощать оба, потому что они будут использоваться в GrandChildComponent.

Вот как вы продолжите передавать состояние в приложении. Ну и что, если у вас есть компоненты, которые все еще глубже в дереве? Как GreatGrandChildи даже GreatGreatGrandChild? Зачем родителям беспокоить свое молодое поколение своими проблемами?

В более крупных приложениях prop drilling может усложнить поддержку и понимание кода. Каждый промежуточный компонент должен знать о подпорках, которые ему нужно передать, даже если он их не использует.

Вот почему существует Context API, позволяющий предотвратить это громоздкое изучение свойств и сделать использование состояния в глубоко вложенных компонентах менее громоздким и более простым.

Как работает API контекста?

Context API предоставляет средства для совместного использования значений, таких как состояние, функции или любые данные, по всему дереву компонентов без передачи props вручную на каждом уровне. Это особенно полезно для глобальных данных, к которым многим компонентам нужен доступ.

Чтобы начать использовать Context API, первое, что вам нужно сделать, это создать контекст с помощью createContext()метода. Эта функция возвращает объект контекста с двумя компонентами – a Providerи a Consumer.

Используется Providerдля обертывания части дерева компонентов, где вы хотите, чтобы контекст был доступен. Он принимает обязательный valueprop, который содержит данные, которые вы хотите совместно использовать с другими компонентами. Когда valueprop изменяется Provider, все потомки, которые потребляют контекст, будут повторно визуализированы.

Позволяет Consumerлюбому дочернему компоненту использовать контекст. Он принимает функцию в качестве дочернего элемента, где аргумент функции — это текущее значение контекста. В современном React useContextхук часто используется вместо Consumerдля лучшей читаемости и простоты.

Как настроить поставщика контекста

Чтобы показать вам, как настроить поставщик контекста, я буду использовать состояние счетчика и setCountфункцию из примера prop drilling.

Помните, что первое, что нужно сделать, это создать контекст с помощью createContextметода. Я сделаю это в context/counterContext.jsфайле. Это соглашение по именованию файла контекста – functionalityContext.jsили .ts.

Вот полные шаги по настройке поставщика контекста:

  • Импорт createContextи useStateиз React
  • Создайте CounterContextконстанту и установите ее значениеcreateContext
  • Передайте значения по умолчаниюcreateContext
  • Создайте CounterProviderкомпонент, который будет приниматьchildren
  • Определите свой stateиsetState
  • Верните a, CounterContext.Providerкоторый примет countи setCountв качестве значений valueсвойства
  • Передача children– представляет все, что должно быть вложено при использовании контекста.
  • Экспорт CounterContextиCounterProvider
import { createContext, useState } from 'react';

const CounterContext = createContext({
 count: 0,
 setCount: () => {},
});

const CounterProvider = ({ children }) => {
 const [count, setCount] = useState(0);

 return (
   <CounterContext.Provider value={{ count, setCount }}>
     {children}
   </CounterContext.Provider>
 );
};

export { CounterContext, CounterProvider };

Этот же процесс применим к любому контексту, который вы хотите создать.

Как использовать контекст в компонентах React

Чтобы использовать контекст, первое, что вам нужно сделать, это импортировать его и поместить в приложение.

Для нашего небольшого приложения-счетчика вы можете сделать это внутри layoutфайла проекта Next JS 14, импортировав данные CounterProviderиз counterContextфайла и заключив их {children}в bodyтег:

import { CounterProvider } from '@/context/counterContext';

export default function RootLayout({
 children,
}: Readonly<{ children: React.ReactNode }>) {
 return (
   <html lang="en">
     <body className={inter.className}>
       {/* Wrap the CounterProvider around the childre */}
       <CounterProvider>{children}</CounterProvider>
     </body>
   </html>
 );
}

Теперь все страницы и компоненты будут иметь доступ к countсостоянию и setCountфункциям.

Теперь внутри GrandChildкомпонента, где используются countсостояние и функция, импортируйте из файла и из него , затем удалите реквизиты.setCountuseContext'react'CounterContextcounterContext

Также вытащите countсостояние setCountиз CounterContextимпортированного вами файла следующим образом:

const { count, setCount } = useContext(CounterContext);

Вы можете оставить countи setCountкак есть, и все будет работать нормально:

'use client';

import { useContext } from 'react';
import { CounterContext } from '@/context/counterContext';

const GrandChildComponent = () => {
 const { count, setCount } = useContext(CounterContext);

 return (
   <div>
     <div className="text-center mt-3">
       <h2 className="text-3xl">Grandchild Component</h2>
       <small>Using the count state</small>
     </div>
     <div className="text-center">
       <h3 className="text-2xl">Count is: {count}</h3>
       <button
         onClick={() => setCount(count + 1)}
         className="bg-pink-600 p-2 rounded text-white"
       >
         Increase Count
       </button>
     </div>
   </div>
 );
};

export default GrandChildComponent;

Все по-прежнему работает отлично:

Изображение пропов в React

Распространенные варианты использования Context API

Context API универсален и может использоваться в различных сценариях, где необходимо управление состоянием и обмен данными между несколькими компонентами. Вот некоторые распространенные варианты использования:

  • Глобальное управление состоянием в средних и крупных приложениях : Context API может осуществлять глобальное управление состоянием, например, элементами корзины в приложении электронной коммерции или текущей воспроизводимой песней в музыкальном приложении.
  • Управление аутентификацией : использование Context API и других подобных решений для управления состоянием аутентификации является обычным вариантом использования. Такие состояния, как текущий пользователь и токены аутентификации, могут быть общими для всего приложения с помощью Context API. Это позволяет любому компоненту получать доступ к состоянию аутентификации пользователя и выполнять такие действия, как вход и выход из системы, или отображать определенные элементы на основе состояния.
  • Управление темами : Еще один популярный вариант использования Context API — управление темами (переключение темного и светлого режимов). Вы можете сделать это, сохранив состояние темы в контексте, а затем получить доступ к теме и обновить ее в любом компоненте без необходимости передавать свойства через несколько слоев.

Другие варианты использования — локализация, пользовательские настройки, такие как настройки уведомлений, открытие, закрытие и переключение состояний модального окна, управление запросами API, навигация по цепочке переходов, пошаговый прогресс и любые другие моменты, где задействовано состояние.

Лучшие практики использования Context API в React

Если вы хотите эффективно использовать Context API, есть несколько рекомендаций, которым нужно следовать, чтобы ваше приложение оставалось поддерживаемым, производительным и масштабируемым. Вот несколько рекомендаций и советов по использованию Context API:

Всегда указывайте значения по умолчанию

Предоставление значений по умолчанию может помочь избежать неопределенных ошибок при использовании контекста за пределами его поставщика.

const UserContext = createContext({
 user: { name: 'Guest', age: null },
 setUser: () => {},
});

Не злоупотребляйте контекстом

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

Это можно сделать, создав несколько контекстов для разных частей вашего приложения вместо одного всеобъемлющего контекста.

const ThemeContext = React.createContext();
const AuthContext = React.createContext();

Это сокращает количество ненужных повторных рендеров и сохраняет модульность управления состоянием.

Избегайте частых обновлений

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

const UserContext = createContext();

const UserProvider = ({ children }) => {
 const [user, setUser] = useState({ name: 'John Doe', age: 30 });
 const [isOnline, setIsOnline] = useState(true); // local state for a frequently changing data

 return (
   <UserContext.Provider value={{ user, setUser }}>
     {children}
   </UserContext.Provider>
 );
};

Используйте пользовательские хуки для инкапсуляции логики

Попытайтесь создать пользовательские хуки для инкапсуляции логики потребления контекста. Это улучшает читаемость кода и возможность повторного использования.

const useUser = () => {
 const context = useContext(UserContext);
 if (!context) {
   throw new Error('useUser must be used within a UserProvider');
 }
 return context;
};

Запоминайте значения контекста

Использование useMemoхука для запоминания значений контекста может помочь предотвратить ненужные повторные визуализации потребляющих компонентов.

const UserProvider = ({ children }) => {
 const [user, setUser] = useState({ name: 'John Doe', age: 30 });

 const value = useMemo(() => ({ user, setUser }), [user, setUser]);

 return <UserContext.Provider value={value}>{children}</UserContext.Provider>;
};

Краткое содержание

В этой статье мы изучили Context API, начав с понимания его потребности и того, как он работает. Используя встречный пример, мы настроили Context Provider и использовали контекст в компоненте, чтобы продемонстрировать его использование.