Привет👋🏻,
Эта статья специально создана для новичков, которые хотят изучить более эффективные методы управления состоянием между несколькими компонентами. Он также направлен на решение распространенной проблемы сверления опор, которая может затруднить поддержку и понимание вашего кода. Начнем с того, какие проблемы решает контекстный API.
Если вы предпочитаете видеоформат, то вот мастер-класс, который вы можете посмотреть на моем канале YouTube.👇🏻
Вы знаете, как иногда вам нужно передать данные из родительского компонента в дочерний, и в конечном итоге вам приходится передавать реквизиты через кучу промежуточных компонентов? Это называется винтовым бурением , и оно может быстро запутаться. Давайте рассмотрим пример, чтобы прояснить это.
Как показано на диаграмме, представьте, что вы получили некоторые данные в компоненте App
, который находится в корне вашего приложения. Теперь, если глубоко вложенному компоненту, скажем, компоненту Grandchild
, необходим доступ к этим данным, вы обычно передаете их через компоненты Parent
и Child
в качестве реквизитов, прежде чем они достигнут Grandchild
. Это может стать неприятным по мере роста вашего приложения.
Вот еще одно визуальное представление:
В приведенном выше примере компоненту Profile
требуются пользовательские данные, но эти данные сначала должны пройти через компоненты App
и Navigation
, даже если эти промежуточные компоненты сами не используют данные. Итак, как нам это очистить? Вот тут-то и пригодится Context API.
Бурение реквизита:
Контекстный API в React.js позволяет передавать данные между компонентами без необходимости передавать их в качестве реквизита на каждом уровне дерева компонентов. Он работает как глобальная система управления состоянием, где вы определяете свое состояние в объекте контекста, а затем можете легко получить к нему доступ в любом месте дерева компонентов. Давайте разберемся в этом на примере.
Как вы можете видеть на диаграмме, у нас есть объект контекста, в котором хранятся данные, к которым могут получить доступ несколько компонентов. Эти данные извлекаются из API или сторонних сервисов. Прежде чем получить доступ к этим данным контекста в любом компоненте, нам необходимо обернуть все компоненты, которым требуются эти данные, в компонент поставщика контекста.
Если нам нужен доступ только к данным в компонентах навигации и профиля, нам не нужно оборачивать компонент приложения. После того как вы обернули соответствующие компоненты в ContextProvider
, вы можете напрямую получить доступ к данным контекста в любом компоненте, который их использует. Не волнуйтесь, если вы еще этого не понимаете; давайте углубимся в код и посмотрим его в действии.
Сначала давайте создадим приложение React, используя Vite.js. Просто скопируйте следующие команды, чтобы настроить проект.
npm create vite@latest
cd project_name // to change to project directory npm install npm run dev
Затем вы можете открыть свой сервер разработки http://localhost:5173
в своем браузере.
Для начала создадим необходимые папки. Вот структура папок нашего проекта.
src | components | context
В папке компонентов создадим файл Profile.jsx
и добавим следующий код.
import React from 'react' const Profile = () => { return ( <div>Profile</div> ) } export default Profile
Создайте еще один компонент под названием Navbar.jsx
в папке компонентов.
import Profile from './Profile' const Navbar = () => { return ( <nav style={{ display: "flex", justifyContent: "space-between", alignItems: "center", width: "90%", height: "10vh", backgroundColor: theme === "light" ? "#fff" : "#1b1b1b", color: theme === "light" ? "#1b1b1b" : "#fff", border: "1px solid #fff", borderRadius: "5px", padding: "0 20px", marginTop: "40px", }}> <h1>LOGO</h1> <Profile /> </nav> ) } export default Navbar
Давайте импортируем этот компонент <Navbar />
в файл App.jsx
.
import Navbar from "./components/Navbar"; function App() { return ( <main style={{ display: "flex", flexDirection: "column", justifyContent: "start", alignItems: "center", height: "100vh", width: "100vw", }} > <Navbar /> </main> ); } export default App;
Таким образом, по сути, компонент <Profile />
является дочерним элементом <Navbar />
а <Navbar />
является дочерним элементом компонента <App />
.
Давайте создадим файл UserContext.jsx
в папке context
. Добавьте в файл следующий код.
import { createContext, useEffect, useState } from "react"; export const UserContext = createContext(); export const UserProvider = ({ children }) => { const [user, setUser] = useState(null); const fetchUserData = async (id) => { const response = await fetch( `https://jsonplaceholder.typicode.com/users/${id}` ).then((response) => response.json()); console.log(response); setUser(response); }; useEffect(() => { fetchUserData(1); }, []); return ( <UserContext.Provider value={{ user, fetchUserData }} > {children} </UserContext.Provider> ); };
UserContext
с помощью createContext
. Мы обязательно импортируем его из react
. Мы можем добавить значения по умолчанию внутри объекта контекста, но пока оставляем его нулевым.
UserProvider
, который возвращает поставщика, использующего UserContext
, например UserContext.Provider
. Он окружает дочерние компоненты, и в качестве значения мы можем передать все, что хотим использовать в дочерних компонентах.
fetchUserData
принимает id
и использует этот идентификатор для получения пользовательских данных. Затем мы сохраняем ответ в user
состоянии.
fetchUserData
в useEffect
, поэтому при загрузке страницы она вызывает функцию и вводит данные в состояние user
.
Теперь давайте используем этот контекст в компоненте <App />
. Оберните компонент <NavBar />
с помощью <UserProvider />
; то же, что следующий код:
<UserProvider> <Navbar /> </UserProvider>
Давайте воспользуемся состоянием user
в компоненте <Profile />
. Для этого мы будем использовать хук useContext
. Это принимает UserContext
и предоставляет значения, которые мы передали в UserProvider
, такие как состояние user
и функция fetchUserData
. Помните, нам не нужно обертывать компонент <Profile />
поскольку он уже находится в компоненте <Navbar />
, который уже обернут провайдером.
Откройте Profile.jsx
и добавьте следующий код.
const { user } = useContext(UserContext); if (user) { return ( <span style={{ fontWeight: "bold", }} > {user.name} </span> ); } else { return <span>Login</span>; }
Здесь мы используем состояние user
из UserContext
. Мы отобразим имя пользователя, если user
есть, в противном случае мы отобразим только сообщение для входа. Теперь, если вы видите результат, в компоненте навигационной панели должно быть имя пользователя. Таким образом мы можем напрямую использовать любое состояние, находящееся в контексте любых компонентов. Компонент, использующий это состояние, должен быть заключен в <Provider />
.
Вы также можете использовать несколько контекстов. Вам просто нужно обернуть компоненты поставщика внутри другого компонента поставщика, как показано в следующем примере.
<ThemeProvider> <UserProvider> <Navbar /> </UserProvider> </ThemeProvider>
В приведенном выше примере мы используем <ThemeProvider />
, который управляет состоянием темы.
Вы можете посмотреть приведенное выше видео на YouTube, чтобы увидеть полный пример использования нескольких поставщиков контекста.
Существует одна проблема, которая возникает при использовании Context API в нескольких компонентах. Всякий раз, когда состояние или значение изменяется в Context API, он повторно отображает все компоненты, подписанные на этот конкретный контекст, даже если не все компоненты используют измененное состояние. Чтобы понять эту проблему повторного рендеринга, давайте создадим компонент <Counter />
, который использует контекст для хранения и отображения значений счетчика.
Посмотрите следующий пример. Вы можете создать файл Counter.jsx
в папке компонентов и вставить следующий код.
import { createContext, memo, useContext, useState } from "react"; const CountContext = createContext(); const CountProvider = ({ children }) => { const [count, setCount] = useState(0); return ( <CountContext.Provider value={{ count, setCount }}> {children} </CountContext.Provider> ); }; function CountTitle() { console.log("This is Count Title component"); return <h1>Counter Title</h1>; } function CountDisplay() { console.log("This is CountDisplay component"); const { count } = useContext(CountContext); return <div>Count: {count}</div>; } function CounterButton() { console.log("This is CounterButton component"); const { count, setCount } = useContext(CountContext); return ( <> <CountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </> ); } export default function Counter() { return ( <CountProvider> <CounterButton /> </CountProvider> ); }
В приведенном выше коде:
CountContext
, используя createContext.
CountProvider,
у нас есть одно состояние для хранения значений счетчика. Мы отправляем count
и метод setCount
дочерним компонентам через свойство value.
Мы создали компоненты отдельно, чтобы увидеть, сколько раз отдельные компоненты перерисовываются.
<CountTitle />
: этот компонент отображает только заголовок и даже не использует никаких значений из контекста.
<CountDisplay />
: этот компонент отображает значения счетчика и использует состояние count
из контекста.
<CounterButton />
: этот компонент отображает как вышеуказанный компонент, так и кнопку, которая увеличивает значения счетчика с помощью setCount
.
В конце мы оборачиваем компонент <CounterButton />
внутри компонента CountProvider
, чтобы другие компоненты могли получить доступ к значениям счетчика.
Теперь, если вы запустите код и нажмете кнопку Increase
, вы увидите в журналах, что каждый компонент выполняет повторную визуализацию каждый раз при изменении состояния. <CountTitle />
даже не использует значения счетчика, но выполняет повторный рендеринг. Это происходит потому, что родительский компонент <CountTitle />
который является <CounterButton />
использует и обновляет значение count и поэтому выполняет повторный рендеринг.
Как мы можем оптимизировать это поведение? Ответ — memo
. memo
React позволяет пропустить повторный рендеринг компонента, если его свойства не изменились. После компонента <CountTitle />
добавим следующую строку.
const MemoizedCountTitle = React.memo(CountTitle)
Теперь в компоненте <CounterButton />
, где мы визуализируем компонент <CountTitle />
, замените <CountTitle />
на <MemoizedCountTitle />
как показано в следующем коде:
<> <MemoizedCountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </>
Теперь, если вы увеличите счетчик и проверите журналы, вы увидите, что компонент <CountTitle />
больше не отображается.
Redux
— это библиотека управления состояниями для комплексного управления состояниями с более предсказуемыми переходами между состояниями. В то время как Context API предназначен для простого управления состоянием и передачи данных через дерево компонентов без детализации свойств. Итак, когда же выбирать?
Существует также еще одна библиотека, которая также является популярным вариантом управления состоянием. Реакция отдачи .
Если вам интересно узнать больше о React Recoil , дайте мне знать в комментариях, и я создам подробные руководства по этой теме на основе ваших отзывов.
Контекстный API React.js
предлагает мощный и эффективный способ управления состояниями нескольких компонентов, эффективно решая проблему бурения винтов. Используя Context API, вы можете упростить свой код, уменьшить количество ненужных повторных рендерингов и повысить общую производительность приложения.
Хотя Context API идеально подходит для простого управления состоянием, более сложные приложения могут извлечь выгоду из использования Redux
или других библиотек управления состоянием, таких как React Recoil
. Понимание того, когда и как использовать эти инструменты, позволит вам создавать более удобные в обслуживании и масштабируемые приложения React.
Спасибо за прочтение этой статьи, надеюсь, она была вам полезна. Если вы заинтересованы в изучении и создании проектов с использованием React, Redux и Next.js, вы можете посетить мой канал на YouTube здесь: CodeBucks.
Вот другие мои статьи, которые вы, возможно, захотите прочитать:
Как реализовать плавную прокрутку в Next.js с помощью Lenis и GSAP
Топ-10 популярных тем VS Code, которые стоит попробовать
Посетите мой личный блог: DevDreaming