paint-brush
Как упростить управление состоянием с помощью контекстного API React.js — учебное пособиек@codebucks
669 чтения
669 чтения

Как упростить управление состоянием с помощью контекстного API React.js — учебное пособие

к CodeBucks11m2024/08/02
Read on Terminal Reader

Слишком долго; Читать

Этот блог предлагает подробное руководство по управлению состоянием в React с помощью Context API. В нем объясняется, как избежать бурения винта, повысить производительность и эффективно реализовать Context API. Благодаря практическим примерам и советам по оптимизации он идеально подходит для разработчиков, стремящихся оптимизировать управление состоянием в своих приложениях React.
featured image - Как упростить управление состоянием с помощью контекстного API React.js — учебное пособие
CodeBucks HackerNoon profile picture
0-item

Привет👋🏻,


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


Если вы предпочитаете видеоформат, то вот мастер-класс, который вы можете посмотреть на моем канале YouTube.👇🏻


Что такое винтовое бурение?

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


Детализация реквизита в React.js

Как показано на диаграмме, представьте, что вы получили некоторые данные в компоненте App , который находится в корне вашего приложения. Теперь, если глубоко вложенному компоненту, скажем, компоненту Grandchild , необходим доступ к этим данным, вы обычно передаете их через компоненты Parent и Child в качестве реквизитов, прежде чем они достигнут Grandchild . Это может стать неприятным по мере роста вашего приложения.


Вот еще одно визуальное представление:


Пример детализации реквизитов Reactjs

В приведенном выше примере компоненту Profile требуются пользовательские данные, но эти данные сначала должны пройти через компоненты App и Navigation , даже если эти промежуточные компоненты сами не используют данные. Итак, как нам это очистить? Вот тут-то и пригодится Context API.


Бурение реквизита:

  • Увеличивает повторный рендеринг компонентов.
  • Увеличивает шаблонный код
  • Создает зависимость компонента
  • Снижает производительность

Контекстный API реагирования

Контекстный API в React.js позволяет передавать данные между компонентами без необходимости передавать их в качестве реквизита на каждом уровне дерева компонентов. Он работает как глобальная система управления состоянием, где вы определяете свое состояние в объекте контекста, а затем можете легко получить к нему доступ в любом месте дерева компонентов. Давайте разберемся в этом на примере.


Контекстный API React.js

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


Если нам нужен доступ только к данным в компонентах навигации и профиля, нам не нужно оборачивать компонент приложения. После того как вы обернули соответствующие компоненты в ContextProvider , вы можете напрямую получить доступ к данным контекста в любом компоненте, который их использует. Не волнуйтесь, если вы еще этого не понимаете; давайте углубимся в код и посмотрим его в действии.


Как использовать контекстный API?

Сначала давайте создадим приложение 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 /> .

Добавление контекстного API

Давайте создадим файл 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 . Он окружает дочерние компоненты, и в качестве значения мы можем передать все, что хотим использовать в дочерних компонентах.


  • Прямо сейчас мы используем API jsonplaceholder для получения пользовательских данных. jsonplaceholder предоставляет поддельные конечные точки API для целей тестирования. Функция 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, чтобы увидеть полный пример использования нескольких поставщиков контекста.

Оптимизация повторного рендеринга в React Context API

Существует одна проблема, которая возникает при использовании 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 против контекстного API

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


  • Используйте React Context API для простого, локализованного управления состоянием, когда состояние не меняется часто.


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


Существует также еще одна библиотека, которая также является популярным вариантом управления состоянием. Реакция отдачи .


  • React Recoil — это библиотека управления состоянием для React, целью которой является обеспечение простоты Context API с мощью и производительностью Redux.


Если вам интересно узнать больше о React Recoil , дайте мне знать в комментариях, и я создам подробные руководства по этой теме на основе ваших отзывов.

Заключение

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


Хотя Context API идеально подходит для простого управления состоянием, более сложные приложения могут извлечь выгоду из использования Redux или других библиотек управления состоянием, таких как React Recoil . Понимание того, когда и как использовать эти инструменты, позволит вам создавать более удобные в обслуживании и масштабируемые приложения React.


Спасибо за прочтение этой статьи, надеюсь, она была вам полезна. Если вы заинтересованы в изучении и создании проектов с использованием React, Redux и Next.js, вы можете посетить мой канал на YouTube здесь: CodeBucks.


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

Посетите мой личный блог: DevDreaming