Una introducción a la API de contexto de React
Publicado: 2022-03-10Para este tutorial, debe tener una comprensión justa de los ganchos. Aún así, antes de comenzar, discutiré brevemente qué son y los ganchos que usaremos en este artículo.
De acuerdo con React Docs:
Los ganchos son una nueva incorporación en React 16.8. Te permiten usar el estado y otras características de React sin escribir una clase”.
Eso es básicamente lo que es un gancho React. Nos permite usar estado, referencias y otras características de React en nuestros componentes funcionales.
Discutamos los dos ganchos que encontraremos en este artículo.
El gancho useState
El enlace useState nos permite usar el estado en nuestros componentes funcionales. Un gancho useState
toma el valor inicial de nuestro estado como único argumento y devuelve una matriz de dos elementos. El primer elemento es nuestra variable de estado y el segundo elemento es una función en la que podemos usar la actualización del valor de la variable de estado.
Echemos un vistazo al siguiente ejemplo:
import React, {useState} from "react"; function SampleComponent(){ const [count, setCount] = useState(0); }
Aquí, count
es nuestra variable de estado y su valor inicial es 0
, mientras que setCount
es una función que podemos usar para actualizar el valor de count.
El gancho useContext
Discutiré esto más adelante en el artículo, pero este gancho básicamente nos permite consumir el valor de un contexto. Lo que esto realmente significa se hará más evidente más adelante en el artículo.
Espacios de trabajo de hilo
Los espacios de trabajo de Yarn le permiten organizar la base de código de su proyecto utilizando un repositorio monolítico (monorepo). React es un buen ejemplo de un proyecto de código abierto que es monorepo y usa espacios de trabajo de Yarn para lograr ese propósito. Leer un artículo relacionado →
¿Por qué necesitamos la API de contexto?
Queremos crear un componente de "alternador de temas" que alterna entre el modo claro y el modo oscuro para nuestra aplicación React. Todos los componentes deben tener acceso al modo de tema actual para que puedan diseñarse en consecuencia.
Normalmente, proporcionaríamos el modo de tema actual a todos los componentes a través de accesorios y actualizaríamos el tema actual usando el state
:
import React from "react"; import ReactDOM from "react-dom"; function App() { return ( <div> <Text theme= "blue" /> <h1>{theme}</h1> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
En el ejemplo de código anterior, creamos un componente de texto que representa un elemento h1
. El color del elemento h1
depende del modo de tema actual. Actualmente, el tema es azul. Podemos alternar entre temas blue
y red
usando state
.
Crearemos un estado llamado "tema" usando el gancho useState
. El useState
devolverá el valor actual del tema y una función que podemos usar para actualizar el tema.
Entonces, vamos a crear nuestro estado de tema:
const [theme, setTheme] = React.useState("blue");
También agregaremos un elemento de botón a nuestro componente de App
. Este botón se usará para alternar los temas y necesita un controlador de eventos de clic. Entonces, escribamos el controlador de eventos de clic así:
const onClickHandler = () => { setTheme(); }
Ahora, queremos establecer el nuevo tema en Red
si el tema actual es Blue
y viceversa. En lugar de usar una declaración if
, una forma más conveniente de hacerlo es con la ayuda del operador ternario en JavaScript.
setTheme( theme === "red"? "blue": "red");
Así que ahora, hemos escrito nuestro controlador onClick
. Agreguemos este elemento de botón al componente de la App
:
<button onClick = {onClickHandler}>Change theme</button>
Cambiemos también el valor de los accesorios del tema del componente Texto al estado del tema.
<Text theme={theme}/>
Ahora, deberíamos tener esto:
import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; function App() { const[theme, setTheme] = React.useState("red"); const onClickHandler = () => { setTheme( theme === "red"? "blue": "red"); } return ( <div> <Text theme={theme}/> <button onClick = {onClickHandler}>Change theme</button> </div> ); } function Text({theme}) { return( <h1 style = {{ color: `${theme}` }}>{theme}</h1> ); } const rootElement = document.getElementById("root"); ReactDOM.render(<App />, rootElement);
Ahora podemos alternar entre nuestros dos temas. Sin embargo, si se tratara de una aplicación mucho más grande, sería difícil utilizar el tema en componentes profundamente anidados y el código se volvería difícil de manejar.
Introducción a la API de contexto
Permítanme presentarles la API de contexto. De acuerdo con la documentación de React:
"El contexto proporciona una forma de pasar datos a través del árbol de componentes sin tener que pasar los accesorios manualmente en cada nivel".
Para una definición más detallada, proporciona una forma de hacer que los datos particulares estén disponibles para todos los componentes en todo el árbol de componentes, sin importar qué tan profundamente anidado pueda estar ese componente.
Veamos este ejemplo:
const App = () => { return( <ParentComponent theme = "light"/> ); } const ParentComponent = (props) => ( <Child theme = {props.theme} /> ) const Child = (props) => ( <Grandchild theme = {props.theme} /> ) const Grandchild = (props) => ( <p>Theme: {props.theme}</p> )
En el ejemplo anterior, especificamos el tema de la aplicación usando accesorios en ParentComponent
llamado theme
. Tuvimos que pasar esos accesorios a todos los componentes del árbol de componentes para llegar a donde se necesita, que es el componente GrandChild
. ChildComponent
no tenía nada que ver con los accesorios del tema, sino que solo se usaba como intermediario.
Ahora, imagine que el componente GrandChild
estaba más profundamente anidado que en el ejemplo superior. Tendríamos que pasar los accesorios del tema de la misma manera que lo hicimos aquí, lo que sería engorroso. Este es el problema que resuelve Context
. Con Context
, cada componente en el árbol de componentes tiene acceso a cualquier dato que decidamos poner en nuestro contexto.
Comencemos con el Context
Es hora de replicar el botón de cambio de tema que construimos al principio del artículo con la API de contexto. Esta vez, nuestro selector de temas será un componente separado. Construiremos un componente ThemeToggler
que cambia el tema de nuestra aplicación React usando Context
.
Primero, inicialicemos nuestra aplicación React. (Prefiero usar create-react-app
pero puedes usar el método que prefieras).
Una vez que haya inicializado su proyecto React, cree un archivo llamado ThemeContext.js en su carpeta /src
. También puede crear una carpeta llamada /context
y colocar su archivo ThemeContext allí si lo desea.
Ahora, sigamos adelante.
Creación de su API de contexto
Crearemos nuestro contexto de tema en nuestro archivo ThemeContext.js .
Para crear un contexto, usamos React.createContext
que crea un objeto de contexto. Puede pasar cualquier cosa como argumento a React.createContext
. En este caso, vamos a pasar una cadena que es el modo de tema actual. Así que ahora nuestro modo de tema actual es el modo de tema "ligero".
import React from "react"; const ThemeContext = React.createContext("light"); export default ThemeContext;
Para que este contexto esté disponible para todos nuestros componentes de React, tenemos que usar un proveedor. ¿Qué es un proveedor? De acuerdo con la documentación de React, cada objeto de contexto viene con un componente Provider React que permite que los componentes de consumo se suscriban a los cambios de contexto. Es el proveedor el que permite que el contexto sea consumido por otros componentes. Dicho esto, creemos nuestro proveedor.
Vaya a su archivo App.js. Para crear nuestro proveedor, tenemos que importar nuestro ThemeContext
.
Una vez que se ha importado el ThemeContext
, tenemos que encerrar el contenido de nuestro componente App
en las etiquetas ThemeContext.Provider
y darle al componente ThemeContext.Provider
un accesorio llamado value
que contendrá los datos que queremos poner a disposición de nuestro árbol de componentes.
function App() { const theme = "light"; return ( <ThemeContext.Provider value = {theme}> <div> </div> </ThemeContext.Provider> ); }
Así que ahora el valor de "luz" está disponible para todos nuestros componentes (que escribiremos pronto).
Creando nuestro archivo de tema
Ahora, crearemos nuestro archivo de tema que contendrá los diferentes valores de color para nuestros temas claros y oscuros. Cree un archivo en su carpeta /src
llamado Colors.js .
En Colors.js , crearemos un objeto llamado AppTheme
. Este objeto contendrá los colores de nuestros temas. Una vez que haya terminado, exporte el objeto AppTheme
así:
const AppTheme = { light: { textColor: "#000", backgroundColor: "#fff" }, dark: { textColor: "#fff", backgroundColor: "#333" } } export default AppTheme;
Ahora es el momento de comenzar a crear nuestros diferentes componentes de React.
Creando nuestros componentes React
Vamos a crear los siguientes componentes:
-
Header
-
ThemeToggler
-
MainWithClass
Encabezado.jsx
import React from "react"; import ThemeToggler from "./ThemeToggler"; const headerStyles = { padding: "1rem", display: "flex", justifyContent: "space-between", alignItems: "center" } const Header = () => { return( <header style = {headerStyles}> <h1>Context API</h1> <ThemeToggler /> </header> ); } export default Header;
ThemeToggler.jsx
(Por ahora, solo devolveremos un div
vacío).
import React from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { return( <div style = {themeTogglerStyle}> </div> ); } export default ThemeToggler;
Consumo de contexto con componentes basados en clases
Aquí, usaremos el valor de nuestro ThemeContext
. Como ya sabrás, tenemos dos métodos para escribir componentes en React : a través de funciones o clases. El proceso de uso del contexto en ambos métodos es diferente, por lo que crearemos dos componentes para que sirvan como la sección principal de nuestra aplicación: MainWithClass
y MainWithFunction
.
Comencemos con MainWithClass
.
MainWithClass.jsx
Tendremos que importar nuestro ThemeContext
y AppTheme
. Una vez hecho esto, escribiremos una clase que devuelva nuestro JSX desde un método de renderizado. Ahora tenemos que consumir nuestro contexto. Hay dos métodos para hacer esto con componentes basados en clases:
- El primer método es a través
Class.contextType
.
Para usar este método, asignamos el objeto de contexto de nuestroThemeContext
a la propiedadcontextType
de nuestra clase. Después de eso, podremos acceder al valor de contexto usandothis.context
. También puede hacer referencia a esto en cualquiera de los métodos de ciclo de vida e incluso en el método de renderizado.import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context]; return( <main></main> ); } }
Después de asignarThemeContext
a la propiedadcontextType
de nuestra clase, guardé el objeto del tema actual en la variablecurrentTheme
.
Ahora, tomaremos los colores de la variablecurrentTheme
y los usaremos para diseñar algunas marcas.render() { const currentTheme = AppTheme[this.context]; return ( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main>
¡Eso es todo! Sin embargo, este método lo limita a consumir solo un contexto. - El segundo método es
ThemeContext.Consumer
que implica el uso de un Consumidor. Cada objeto de contexto también viene con un componente Consumer React que se puede usar en un componente basado en clases. El componente consumidor toma un hijo como función y esa función devuelve un nodo React. El valor de contexto actual se pasa a esa función como un argumento.
Ahora, reemplacemos el código en nuestro componenteMainWithClass
con esto:class Main extends Component { constructor() { super(); this.state = { } } render(){ return( <ThemeContext.Consumer> { (theme) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } }
Como puede ver, usamos el valor actual de nuestroThemeContext
que llamamos "tema" y tomamos los valores de color para ese modo de tema y lo asignamos a la variablecurrentTheme
. Con este método, puede utilizar varios consumidores.
Esos son los dos métodos para consumir contexto con componentes basados en clases.
Contexto de consumo con componentes funcionales
Consumir contexto con componentes funcionales es más fácil y menos tedioso que hacerlo con componentes basados en clases. Para consumir contexto en un componente funcional, usaremos un gancho llamado useContext
.
Así es como se vería consumir nuestro ThemeContext
con un componente funcional:
const Main = () => { const theme = useContext(ThemeContext); const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;
Como puede ver, todo lo que tuvimos que hacer fue usar nuestro useContext
con nuestro ThemeContext
pasado como argumento.
Nota : debe usar estos diferentes componentes en el archivo App.js para ver los resultados.
Actualización de nuestro tema con el componente ThemeToggler
Ahora vamos a trabajar en nuestro componente ThemeToggler
. Necesitamos poder cambiar entre los temas claros y oscuros. Para hacer esto, necesitaremos editar nuestro ThemeContext.js . Nuestro React.createContext
ahora tomará un objeto parecido al resultado de un gancho useState
como argumento.
const ThemeContext = React.createContext(["light", () => {}]);
Pasamos una matriz a la función React.createContext
. El primer elemento de la matriz es el modo de tema actual y el segundo elemento es la función que se usaría para actualizar el tema. Como dije, esto simplemente se parece al resultado de un gancho useState
pero no es exactamente el resultado de un gancho useState
.
Ahora editaremos nuestro archivo App.js. Necesitamos cambiar el valor pasado al proveedor a un gancho useState
. Ahora el valor de nuestro Theme Context es un gancho useState
cuyo valor predeterminado es "light".
function App() { const themeHook = useState("light"); return ( <ThemeContext.Provider value = {themeHook}> <div> <Header /> <Main /> </div> </ThemeContext.Provider> ); }
Escribiendo nuestro componente ThemeToggler
Escribamos ahora nuestro componente ThemeToggler
:
import React,{useContext} from "react"; import ThemeContext from "../Context/ThemeContext"; const themeTogglerStyle = { cursor: "pointer" } const ThemeToggler = () => { const[themeMode, setThemeMode] = useContext(ThemeContext); return( <div style = {themeTogglerStyle} onClick = {() => {setThemeMode(themeMode === "light"? "dark": "light")}}> <span title = "switch theme"> {themeMode === "light" ? "" : "️"} </span> </div> ); } export default ThemeToggler;
Dado que el valor de nuestro contexto de tema ahora es un gancho cada vez que llamamos a useContext
, devolverá una matriz. Usando la desestructuración, pudimos tomar los elementos de la matriz. Luego escribimos un controlador de eventos onClick
para nuestro ThemeToggler
. Con ese código, cada vez que se haga clic en el conmutador de tema, cambiará el tema de nuestra aplicación.
Ahora editaremos las diferentes versiones de nuestro componente Main
.
Editando nuestro componente MainWithClass
- La versión del componente
MainWithClass
que usa el métodoClass.contextType
:import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component{ constructor(){ super(); } static contextType = ThemeContext; render(){ const currentTheme = AppTheme[this.context[0]]; return( <main style={{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } }
- La versión del componente
MainWithClass
que usa el métodoThemeContext.Consumer
:import React, { Component } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; class Main extends Component { constructor() { super(); this.state = {} } render() { return ( <ThemeContext.Consumer> { ([theme]) => { const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ) } } </ThemeContext.Consumer> ); } } export default Main;
Editando nuestro componente MainWithFunction
El componente MainWithFunction
debe editarse de la siguiente manera:
import React, { useContext } from "react"; import ThemeContext from "../Context/ThemeContext"; import AppTheme from "../Colors"; const Main = () => { const theme = useContext(ThemeContext)[0]; const currentTheme = AppTheme[theme]; return( <main style = {{ padding: "1rem", backgroundColor: `${currentTheme.backgroundColor}`, color: `${currentTheme.textColor}`, }}> <h1>Heading 1</h1> <p>This is a paragraph</p> <button> This is a button</button> </main> ); } export default Main;
Conclusión
¡Eso es todo! Hemos logrado implementar dos modos de tema para nuestra aplicación React utilizando la API de contexto.
En el proceso, hemos aprendido:
- Qué es la API de contexto y el problema que resuelve;
- Cuándo usar la API de contexto;
- Crear
Context
y consumirlo tanto en componentes funcionales como basados en clases.
Lectura adicional en SmashingMag:
- Estilo en aplicaciones web modernas
- Creación de aplicaciones móviles con Ionic y React
- Cree una PWA con Webpack y Workbox
- Introducción a la API de MutationObserver