Hola👋🏻,
Este artículo está creado específicamente para principiantes que desean aprender métodos más efectivos para administrar el estado entre múltiples componentes. También tiene como objetivo abordar el problema común de la perforación de puntales, que puede hacer que su código sea más difícil de mantener y comprender. Comencemos con qué tipo de problema resuelve la API de contexto.
Si prefieres el formato de vídeo, aquí tienes el tutorial que puedes ver en mi canal de YouTube.👇🏻
¿Sabes que a veces necesitas pasar datos de un componente principal a un componente secundario y terminas pasando accesorios a través de un montón de componentes intermedios? Eso se llama perforación de puntal y puede resultar complicado rápidamente. Veamos un ejemplo para aclarar esto.
Como se muestra en el diagrama, imagine que ha obtenido algunos datos en el componente App
, que se encuentra en la raíz de su aplicación. Ahora, si un componente profundamente anidado, digamos el componente Grandchild
, necesita acceder a estos datos, normalmente los transmitirá a través de los componentes Parent
e Child
como accesorios antes de que llegue Grandchild
. Esto puede volverse feo a medida que su aplicación crece.
Aquí hay otra representación visual:
En el ejemplo anterior, el componente Profile
necesita datos del usuario, pero estos datos deben viajar primero a través de los componentes App
y Navigation
, aunque estos componentes intermedios no usan los datos por sí mismos. Entonces, ¿cómo limpiamos esto? Ahí es donde la API Context resulta útil.
Perforación de puntales:
La API de contexto en React.js le permite pasar datos entre componentes sin necesidad de pasarlos como accesorios a través de cada nivel del árbol de componentes. Funciona como un sistema de gestión de estado global en el que usted define su estado en un objeto de contexto y luego puede acceder fácilmente a él en cualquier parte del árbol de componentes. Entendamos esto con un ejemplo.
Como puede ver en el diagrama, tenemos un objeto de contexto que almacena datos a los que pueden acceder varios componentes. Estos datos se obtienen de API o servicios de terceros. Antes de acceder a estos datos de contexto en cualquier componente, debemos empaquetar todos los componentes que requieren estos datos en un componente de proveedor de contexto.
Si solo necesitamos acceder a los datos en los componentes de navegación y perfil, no necesitamos finalizar el componente de la aplicación. Una vez que haya empaquetado los componentes relevantes con ContextProvider
, puede acceder directamente a los datos de contexto en cualquier componente que los consuma. No te preocupes si aún no lo entiendes; Profundicemos en el código y veámoslo en acción.
Primero, creemos una aplicación React usando Vite.js. Simplemente copie los siguientes comandos para configurar el proyecto.
npm create vite@latest
cd project_name // to change to project directory npm install npm run dev
Luego puede abrir su servidor de desarrollo http://localhost:5173
en su navegador.
Primero, creemos las carpetas necesarias. Aquí está la estructura de carpetas de nuestro proyecto.
src | components | context
En la carpeta de componentes, creemos el archivo Profile.jsx
y agreguemos el siguiente código.
import React from 'react' const Profile = () => { return ( <div>Profile</div> ) } export default Profile
Cree un componente más llamado Navbar.jsx
en la carpeta de componentes.
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
Importemos este componente <Navbar />
en el archivo 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;
Básicamente, el componente <Profile />
es hijo de <Navbar />
y <Navbar />
es hijo del componente <App />
.
Creemos el archivo UserContext.jsx
en la carpeta context
. Agregue el siguiente código al archivo.
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
vacío usando createContext
. Nos aseguramos de importarlo desde react
. Podemos agregar valores predeterminados dentro del objeto de contexto, pero lo mantenemos nulo por ahora.
UserProvider
, que devuelve un proveedor usando UserContext
, como UserContext.Provider
. Envuelve los componentes secundarios y, en el valor, podemos pasar cualquier cosa que queramos usar en los componentes secundarios.
fetchUserData
acepta id
y la utiliza para recuperar los datos del usuario. Luego almacenamos la respuesta en el estado user
.
fetchUserData
en useEffect
por lo que al cargar la página, llama a la función e inyecta los datos en el estado user
.
Ahora, usemos este contexto en el componente <App />
. Envuelva el componente <NavBar />
usando <UserProvider />
; Lo mismo que el siguiente código:
<UserProvider> <Navbar /> </UserProvider>
Usemos el estado user
en el componente <Profile />
. Para eso, usaremos el gancho useContext
. Eso toma UserContext
y proporciona los valores que hemos pasado en UserProvider
, como el estado user
y la función fetchUserData
. Recuerde, no necesitamos empaquetar el componente <Profile />
ya que ya está en el componente <Navbar />
que ya está empaquetado con el proveedor.
Abra Profile.jsx
y agregue el siguiente código.
const { user } = useContext(UserContext); if (user) { return ( <span style={{ fontWeight: "bold", }} > {user.name} </span> ); } else { return <span>Login</span>; }
Aquí, estamos usando el estado user
del UserContext
. Mostraremos el nombre de usuario si hay user
; de lo contrario, mostraremos solo un mensaje de inicio de sesión. Ahora, si ve el resultado, debería haber un nombre de usuario en el componente de la barra de navegación. Así es como podemos usar directamente cualquier estado que esté en el contexto de cualquier componente. El componente que utiliza este estado debe incluirse dentro de <Provider />
.
También puede utilizar múltiples contextos. Solo necesita envolver los componentes del proveedor dentro de otro componente del proveedor como se muestra en el siguiente ejemplo.
<ThemeProvider> <UserProvider> <Navbar /> </UserProvider> </ThemeProvider>
En el ejemplo anterior, utilizamos <ThemeProvider />
que gestiona el estado del tema.
Puede ver el vídeo de YouTube anterior para ver el ejemplo completo del uso de múltiples proveedores de contexto.
Hay un problema que ocurre cuando usa la API Context en múltiples componentes. Siempre que el estado o el valor cambia en la API de contexto, vuelve a representar todos los componentes suscritos a ese contexto en particular, incluso si no todos los componentes están utilizando el estado modificado. Para comprender este problema de reproducción, creemos un componente <Counter />
que utilice el contexto para almacenar y mostrar valores de recuento.
Mira el siguiente ejemplo. Puede crear un archivo Counter.jsx
en la carpeta de componentes y pegar el siguiente código.
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> ); }
En el código anterior:
CountContext
usando createContext.
CountProvider,
tenemos un estado para almacenar valores de recuento. Estamos enviando count
y el método setCount
a los componentes secundarios a través de value prop.
Hemos creado componentes por separado para ver cuántas veces se vuelven a renderizar los componentes individuales.
<CountTitle />
: este componente muestra solo el título y ni siquiera utiliza ningún valor del contexto.
<CountDisplay />
: este componente muestra valores de recuento y utiliza el estado count
del contexto.
<CounterButton />
: este componente representa tanto el componente anterior como un botón que aumenta los valores de recuento usando setCount
.
Al final, envolvemos el componente <CounterButton />
dentro del componente CountProvider
para que los otros componentes puedan acceder a los valores de recuento.
Ahora, si ejecuta el código y hace clic en el botón Increase
, verá en los registros que cada componente se vuelve a representar cada vez que cambia el estado. <CountTitle />
ni siquiera utiliza valores de recuento, pero se está volviendo a representar. Esto sucede porque el componente principal de <CountTitle />
que es <CounterButton />
está usando y actualizando el valor de recuento y es por eso que se vuelve a representar.
¿Cómo podemos optimizar este comportamiento? La respuesta es memo
. La memo
de React le permite omitir volver a renderizar un componente cuando sus accesorios no han cambiado. Después del componente <CountTitle />
, agreguemos la siguiente línea.
const MemoizedCountTitle = React.memo(CountTitle)
Ahora, en el componente <CounterButton />
, donde estamos representando el componente <CountTitle />
, reemplace <CountTitle />
con <MemoizedCountTitle />
como en el siguiente código:
<> <MemoizedCountTitle /> <CountDisplay /> <button onClick={() => setCount(count + 1)}>Increase</button> </>
Ahora, si aumenta el recuento y verifica los registros, debería poder ver que ya no representa el componente <CountTitle />
.
Redux
es una biblioteca de gestión de estados para una gestión de estados compleja con transiciones de estado más predecibles. Mientras que la API Context está diseñada para una gestión de estado sencilla y para pasar datos a través del árbol de componentes sin necesidad de profundizar. Entonces, ¿cuándo elegir cuál?
También hay otra biblioteca que también es una opción popular para la gestión estatal. El retroceso de reacción .
Si está interesado en aprender más sobre React Recoil , hágamelo saber en los comentarios y crearé tutoriales detallados sobre este tema en función de sus comentarios.
La API de contexto React.js
ofrece una forma poderosa y eficiente de administrar estados en múltiples componentes, abordando de manera efectiva el problema de la perforación de puntales. Al utilizar la API Context, puede simplificar su código, reducir las reproducciones innecesarias y mejorar el rendimiento general de la aplicación.
Si bien la API Context es ideal para una administración de estado simple, las aplicaciones más complejas pueden beneficiarse del uso de Redux
u otras bibliotecas de administración de estado como React Recoil
. Comprender cuándo y cómo utilizar estas herramientas le permitirá crear aplicaciones React más escalables y fáciles de mantener.
Gracias por leer este artículo, espero que te haya resultado útil. Si está interesado en aprender y crear proyectos usando React, Redux y Next.js, puede visitar mi canal de YouTube aquí: CodeBucks
Aquí están mis otros artículos que quizás le guste leer:
Cómo implementar un desplazamiento suave en Next.js con Lenis y GSAP
Los 10 temas más populares de VS Code que deberías probar
Visita mi blog personal: DevDreaming