API de contexto de Redux vs. React



En React 16.3, se ha agregado una nueva API de contexto. Nuevo en el sentido de que la antigua API de contexto estaba detrás de escena, la mayoría de las personas no sabían acerca de su existencia o no la usaban, porque la documentación aconsejaba evitar usarla.

Sin embargo, ahora la API de contexto es una parte completa de React, abierta para su uso (no como antes, oficialmente).

Inmediatamente después del lanzamiento de React 16.3, aparecieron artículos que proclamaban la muerte de Redux debido a la nueva API de contexto. Si le hubiera preguntado a Redux sobre esto, creo que él habría respondido: "los informes de mi muerte son muy exagerados ".

En esta publicación, quiero hablar sobre cómo funciona la nueva API de Context, cómo se ve Redux, cuándo puedes usar Context en lugar de Redux, y por qué Context no reemplaza a Redux en cada caso.

Si solo desea una descripción general de la API de contexto, puede seguir el enlace .

Ejemplo de aplicación de reacción


Voy a sugerirle que comprenda los principios de trabajar con el estado en React (accesorios y estado), pero si no es así, tengo un curso gratuito de 5 días para ayudarlo a aprenderlo .

Veamos un ejemplo que nos lleva al concepto utilizado en Redux. Comenzaremos con una versión simple de React, y luego veremos cómo se ve en Redux y finalmente con Context.



En esta aplicación, la información del usuario se muestra en dos lugares: en la barra de navegación en la esquina superior derecha y en el panel lateral al lado del contenido principal.

(Puede notar que hay muchas similitudes con Twitter. ¡No es una coincidencia! Una de las mejores formas de perfeccionar sus habilidades de Reacción es copiar (crear réplicas de sitios / aplicaciones existentes) .

La estructura del componente se ve así:



Usando React puro (solo accesorios), necesitamos almacenar información del usuario lo suficientemente alta en el árbol para que pueda pasar a los componentes que la necesitan. En este caso, la información del usuario debe estar en la aplicación.

Luego, para transferir información sobre el usuario a los componentes que la necesitan, la aplicación debe pasarla a Nav y Body. Ellos, a su vez, lo pasarán a UserAvatar (¡hurra!) Y a Sidebar. Finalmente, Sidebar debería pasarlo a UserStats.

Veamos cómo funciona esto en el código (pongo todo en un archivo para que sea más fácil de leer, pero de hecho probablemente se dividirá en archivos separados, siguiendo una estructura estándar ).

import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const UserAvatar = ({ user, size }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> ); const UserStats = ({ user }) => ( <div className="user-stats"> <div> <UserAvatar user={user} /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> ); const Nav = ({ user }) => ( <div className="nav"> <UserAvatar user={user} size="small" /> </div> ); const Content = () => <div className="content">main content here</div>; const Sidebar = ({ user }) => ( <div className="sidebar"> <UserStats user={user} /> </div> ); const Body = ({ user }) => ( <div className="body"> <Sidebar user={user} /> <Content user={user} /> </div> ); class App extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { const { user } = this.state; return ( <div className="app"> <Nav user={user} /> <Body user={user} /> </div> ); } } ReactDOM.render(<App />, document.querySelector("#root")); 


Código de muestra de CodeSandbox

Aquí la aplicación inicializa el estado que contiene el objeto de usuario. En una aplicación real, es más probable que extraiga estos datos del servidor y los guarde en estado para su representación.

En cuanto a los accesorios ("perforación de accesorios"), no es gran cosa . Funciona muy bien Lanzando accesorios, este es un ejemplo ideal de React. Pero arrojar profundamente en el árbol de estado puede ser un poco molesto al escribir. Y molesta aún más si tienes que transmitir muchos props'ov (y no uno).

Sin embargo, hay una gran desventaja en esta estrategia: crea una conexión entre componentes que no deberían conectarse. En el ejemplo anterior, Nav debería aceptar un accesorio de "usuario" y pasarlo a UserAvatar, incluso si Nav no lo necesita.

Los componentes estrechamente acoplados (como los que pasan accesorios a sus hijos) son más difíciles de reutilizar, ya que debe vincularlos con los nuevos padres cada vez que los use en un lugar nuevo.

Veamos cómo podemos mejorar esto.

Antes de usar Context o Redux ...


Si puede encontrar una manera de combinar la estructura de su aplicación y aprovechar la transferencia de accesorios a los descendientes, esto puede hacer que su código sea más limpio sin tener que recurrir a un accesorio avanzado, Context o Redux .

En este ejemplo, los accesorios para niños son una excelente solución para componentes que deben ser universales, como Nav, Sidebar y Body. También tenga en cuenta que puede pasar JSX a cualquier accesorio, no solo a los niños; por lo tanto, si necesita más de una "ranura" para conectar componentes, recuerde esto.

Aquí hay un ejemplo de una aplicación React en la que Nav, Body y Sidebar toman a los niños y los muestran tal como están. Por lo tanto, quien usa el componente no necesita preocuparse por transferir ciertos datos que requiere el componente. Simplemente puede mostrar lo que necesita en su lugar, utilizando los datos que ya tiene dentro del alcance. Este ejemplo también muestra cómo usar cualquier accesorio para transmitir niños.

(¡Gracias a Dan Abramov por esta oferta !)

 import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; const UserAvatar = ({ user, size }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> ); const UserStats = ({ user }) => ( <div className="user-stats"> <div> <UserAvatar user={user} /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> ); //  children   . const Nav = ({ children }) => ( <div className="nav"> {children} </div> ); const Content = () => ( <div className="content">main content here</div> ); const Sidebar = ({ children }) => ( <div className="sidebar"> {children} </div> ); // Body   sidebar  content,    , //    . const Body = ({ sidebar, content }) => ( <div className="body"> <Sidebar>{sidebar}</Sidebar> {content} </div> ); class App extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { const { user } = this.state; return ( <div className="app"> <Nav> <UserAvatar user={user} size="small" /> </Nav> <Body sidebar={<UserStats user={user} />} content={<Content />} /> </div> ); } } ReactDOM.render(<App />, document.querySelector("#root")); 


Código de muestra de CodeSandbox

Si su aplicación es demasiado complicada (¡más complicada que este ejemplo!), Puede ser difícil entender cómo adaptar la plantilla a los niños. Veamos cómo puede reemplazar el reenvío de accesorios con Redux.

Ejemplo de Redux


Echaré un vistazo rápido al ejemplo de Redux para que podamos entender mejor cómo funciona Context, así que si no tienes una comprensión clara de Redux, primero lee mi introducción a Redux (o mira el video ).

Aquí está nuestra aplicación React rediseñada para usar Redux. La información del usuario se ha trasladado a la tienda Redux, lo que significa que podemos usar la función de conexión react-redux para pasar directamente la utilería del usuario a los componentes que los necesitan.

Esta es una gran victoria en términos de deshacerse de la conexión. Eche un vistazo a Nav, Body y Sidebar, y verá que ya no reciben ni transmiten accesorios de usuario. Ya no juegan papas calientes con accesorios. No más conexiones inútiles.

El reductor hace poco aquí; Es bastante simple. Tengo una cosa más sobre cómo funcionan los reductores Redux y cómo escribir el código inmutable que usan.

 import React from "react"; import ReactDOM from "react-dom"; //    createStore, connect, and Provider: import { createStore } from "redux"; import { connect, Provider } from "react-redux"; //  reducer       . const initialState = {}; function reducer(state = initialState, action) { switch (action.type) { //    action SET_USER  state. case "SET_USER": return { ...state, user: action.user }; default: return state; } } //  store  reducer'   . const store = createStore(reducer); // Dispatch' action     user. store.dispatch({ type: "SET_USER", user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }); //   mapStateToProps,      state (user) //     `user` prop. const mapStateToProps = state => ({ user: state.user }); //  UserAvatar    connect(),    //`user` ,      . //     2 : // const UserAvatarAtom = ({ user, size }) => ( ... ) // const UserAvatar = connect(mapStateToProps)(UserAvatarAtom); const UserAvatar = connect(mapStateToProps)(({ user, size }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> )); //   UserStats    connect(),    // `user` . const UserStats = connect(mapStateToProps)(({ user }) => ( <div className="user-stats"> <div> <UserAvatar /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> )); //    Nav      `user`. const Nav = () => ( <div className="nav"> <UserAvatar size="small" /> </div> ); const Content = () => ( <div className="content">main content here</div> ); //   Sidebar. const Sidebar = () => ( <div className="sidebar"> <UserStats /> </div> ); //   Body. const Body = () => ( <div className="body"> <Sidebar /> <Content /> </div> ); //  App    ,     . const App = () => ( <div className="app"> <Nav /> <Body /> </div> ); //     Provider, //   connect()    store. ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.querySelector("#root") ); 


Código de muestra de CodeSandbox

Ahora probablemente te estés preguntando cómo Redux logra esta magia. Asombroso ¿Cómo React no admite pasar accesorios a múltiples niveles, y puede Redux hacer esto?

La respuesta es que Redux usa la función de contexto React (función de contexto). No la API Context moderna (todavía no), sino la antigua. Uno que la documentación de React dice que no use a menos que esté escribiendo su propia biblioteca o sepa lo que está haciendo.

El contexto es similar a un bus de computadora que sigue a cada componente: para que la energía (datos) pase a través de él, solo necesita conectarse. Y react-redux connect hace exactamente eso.

Sin embargo, esta característica de Redux es solo la punta del iceberg. La transferencia de datos al lugar correcto es la característica más obvia de Redux. Aquí hay algunos otros beneficios que obtiene de la caja:

conectar es una función pura

connect automáticamente hace que los componentes conectados sean "limpios", es decir, solo se volverán a renderizar cuando cambien sus accesorios, es decir, cuando cambie su porción del estado de Redux. Esto evita la reproducción innecesaria y acelera la aplicación.

Depuración fácil con Redux

La ceremonia de escribir acciones y reductores se equilibra con la increíble facilidad de depuración que Redux te brinda.

Con la extensión Redux DevTools, obtiene un registro automático de todas las acciones realizadas por su aplicación. En cualquier momento, puede abrirlo y ver qué acciones se lanzaron, cuál es su carga útil y establecer antes y después de la acción.



Otra gran característica que proporciona Redux DevTools es la depuración mediante el "viaje en el tiempo" , es decir, puede hacer clic en cualquier acción anterior e ir a este punto en el tiempo, hasta la actual. La razón por la que esto funciona es que cada acción actualiza la tienda de la misma manera , por lo que puede tomar una lista de las actualizaciones de estado grabadas y reproducirlas sin efectos secundarios, y terminar en el lugar que desee.

También hay herramientas como LogRocket , que básicamente le proporcionan las RedTo DevTools permanentes en producción para cada uno de sus usuarios. ¿Tienes un informe de error? No hay problema Mire esta sesión de usuario en LogRocket, y puede ver una repetición de lo que hizo y qué acciones se lanzaron. Todo esto funciona utilizando el flujo de acción de Redux.

Extendiendo Redux con Middleware

Redux admite el concepto de middleware (una palabra elegante para "una función que se ejecuta cada vez que se envía una acción"). Escribir su propio middleware no es tan difícil como parece, y le permite usar algunas herramientas poderosas.

Por ejemplo ...

  • ¿Desea enviar una solicitud de API cada vez que un nombre de acción comienza con FETCH_? Puedes hacer esto con middleware.
  • ¿Desea un lugar centralizado para registrar eventos en su software de análisis? Middleware es un buen lugar para hacer esto.
  • ¿Quiere evitar que una acción comience en un momento específico? Puede hacer esto con middleware, invisible para el resto de su aplicación.
  • ¿Desea interceptar una acción que tenga un token JWT y guardarla automáticamente en localStorage? Si, middleware.

Aquí hay un buen artículo con ejemplos de cómo escribir el middleware de Redux.

Cómo usar la API React Context


Pero tal vez no necesites todas estas rarezas de Redux. Es posible que no necesite una simple depuración, ajuste o mejoras de rendimiento automáticas; todo lo que desea hacer es transferir datos fácilmente. Tal vez su aplicación es pequeña, o simplemente necesita hacer algo rápidamente y lidiar con las sutilezas más adelante.

La nueva API de contexto probablemente sea adecuada para usted. Veamos como funciona.

Publiqué un tutorial rápido de API de contexto en Egghead si prefieres mirar antes que leer (3:43).

Aquí hay 3 componentes importantes de la API de contexto:

  • Función React.createContext que crea contexto
  • Proveedor (devuelve createContext), que configura el "bus",
    pasando por el árbol de componentes
  • Consumidor (también devuelve createContext) que absorbe
    "Bus eléctrico" para extracción de datos

El proveedor es muy similar al proveedor en React-Redux. Asume un valor que puede ser lo que quieras (incluso podría ser una tienda Redux ... pero eso sería estúpido). Lo más probable es que este sea un objeto que contenga sus datos y cualquier acción que desee realizar con los datos.

Consumer funciona un poco como la función de conexión en React-Redux, conectándose a los datos y poniéndolos a disposición de un componente que los usa.

Aquí están los aspectos más destacados:

 //     context //    2 : { Provider, Consumer } // ,   ,  UpperCase,  camelCase //  ,          , //        . const UserContext = React.createContext(); // ,     context, //   Consumer. // Consumer   "render props". const UserAvatar = ({ size }) => ( <UserContext.Consumer> {user => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> )} </UserContext.Consumer> ); // ,      "user prop", //   Consumer    context. const UserStats = () => ( <UserContext.Consumer> {user => ( <div className="user-stats"> <div> <UserAvatar user={user} /> {user.name} </div> <div className="stats"> <div>{user.followers} Followers</div> <div>Following {user.following}</div> </div> </div> )} </UserContext.Consumer> ); // ...    ... // ... (      `user`). //  App   context ,  Provider. class App extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { return ( <div className="app"> <UserContext.Provider value={this.state.user}> <Nav /> <Body /> </UserContext.Provider> </div> ); } } 


Código de muestra de CodeSandbox

Veamos cómo funciona.

Recuerde, tenemos 3 partes: el contexto mismo (creado usando React.createContext) y dos componentes que interactúan con él (Proveedor y Consumidor).

Proveedor y consumidor trabajan juntos

Proveedor y consumidor están relacionados e inseparables. Solo saben cómo interactuar entre ellos. Si creó dos contextos separados, por ejemplo, "Contexto1" y "Contexto2", el Proveedor1 y el Contexto del consumidor1 no podrán comunicarse con el Contexto2 del proveedor y el consumidor.

El contexto no contiene estado

Tenga en cuenta que el contexto no tiene su propio estado . Este es solo un canal para sus datos. Debe pasar el valor al Proveedor, y ese valor se pasará a cualquier Consumidor que sepa cómo buscarlo (el Proveedor está vinculado al mismo contexto que el Consumidor).

Cuando crea un contexto, puede pasar el "valor predeterminado" de la siguiente manera:

 const Ctx = React.createContext(yourDefaultValue); 


El valor predeterminado es lo que recibirá el Consumidor cuando se coloque en el árbol sin el Proveedor encima. Si no lo pasa, el valor será indefinido. Tenga en cuenta que este es el valor predeterminado , no el valor inicial . El contexto no guarda nada; solo difunde los datos que le pasas.

El consumidor utiliza el patrón de accesorios de renderizado

La función conectar Redux es un componente de orden superior (abreviado como HoC). Envuelve otro componente y le pasa accesorios.

El consumidor, por el contrario, espera que el componente hijo sea una función. Luego llama a esta función durante la representación, pasando el valor que recibió del Proveedor en algún lugar por encima (ya sea el valor predeterminado del contexto, o indefinido si no pasó el valor predeterminado).

El proveedor toma un valor.

Solo un valor, como prop. Pero recuerde que el valor puede ser cualquier cosa. En la práctica, si desea pasar varios valores hacia abajo, debe crear un objeto con todos los valores y pasar este objeto hacia abajo.

API de contexto flexible


Dado que crear contexto nos da dos componentes para trabajar (Proveedor y Consumidor), podemos usarlos como queramos. Aquí hay un par de ideas.

Envuelva al consumidor en HOC

¿No le gusta la idea de agregar UserContext.Consumer en cada lugar que lo necesita? Este es tu código! Tiene derecho a decidir cuál será su mejor opción.

Si prefiere obtener el valor como accesorio, puede escribir un pequeño contenedor alrededor del Consumidor de la siguiente manera:

 function withUser(Component) { return function ConnectedComponent(props) { return ( <UserContext.Consumer> {user => <Component {...props} user={user}/>} </UserContext.Consumer> ); } } 

Después de eso, puede reescribir, por ejemplo, UserAvatar usando la función withUser:

 const UserAvatar = withUser(({ size, user }) => ( <img className={`user-avatar ${size || ""}`} alt="user avatar" src={user.avatar} /> )); 

Y listo, el contexto puede funcionar como conectar Redux. Menos limpieza automática.

Aquí hay un ejemplo de CodeSandbox con este HOC.

Mantener el estado en el proveedor

Recuerde que el proveedor es solo un canal. No guarda ningún dato. Pero esto no le impide crear su propio contenedor para almacenar datos.

En el ejemplo anterior, los datos se almacenan en la aplicación, por lo que lo único que necesitaba comprender eran los componentes Proveedor + Consumidor. Pero tal vez quieras crear tu propia tienda. Puede crear un componente para almacenar el estado y pasarlo a través del contexto:

 class UserStore extends React.Component { state = { user: { avatar: "https://www.gravatar.com/avatar/5c3dd2d257ff0e14dbd2583485dbd44b", name: "Dave", followers: 1234, following: 123 } }; render() { return ( <UserContext.Provider value={this.state.user}> {this.props.children} </UserContext.Provider> ); } } // ...    ... const App = () => ( <div className="app"> <Nav /> <Body /> </div> ); ReactDOM.render( <UserStore> <App /> </UserStore>, document.querySelector("#root") ); 

Ahora los datos del usuario están contenidos en su propio componente, cuya única tarea son estos datos. Genial La aplicación puede volver a ser apátrida. Creo que se ve un poco más limpio.

Aquí hay un ejemplo de CodeSandbox con este UserStore.

Lanzar acciones hacia abajo a través del contexto

Recuerde que el objeto pasado a través del proveedor puede contener todo lo que desee. Esto significa que puede contener funciones. Incluso puedes nombrarlos acciones.

Aquí hay un nuevo ejemplo: una habitación simple con un interruptor para cambiar el color de fondo - oh, me refiero a la luz.



El estado se almacena en la tienda, que también tiene una función de cambio de luz. Tanto el estado como la función se pasan a través del contexto.

 import React from "react"; import ReactDOM from "react-dom"; import "./styles.css"; //  context. const RoomContext = React.createContext(); // ,     //   . class RoomStore extends React.Component { state = { isLit: false }; toggleLight = () => { this.setState(state => ({ isLit: !state.isLit })); }; render() { //  state  onToggleLight action return ( <RoomContext.Provider value={{ isLit: this.state.isLit, onToggleLight: this.toggleLight }} > {this.props.children} </RoomContext.Provider> ); } } //    ,    , //       RoomContext. const Room = () => ( <RoomContext.Consumer> {({ isLit, onToggleLight }) => ( <div className={`room ${isLit ? "lit" : "dark"}`}> The room is {isLit ? "lit" : "dark"}. <br /> <button onClick={onToggleLight}>Flip</button> </div> )} </RoomContext.Consumer> ); const App = () => ( <div className="app"> <Room /> </div> ); //     RoomStore, //           . ReactDOM.render( <RoomStore> <App /> </RoomStore>, document.querySelector("#root") ); 

Aquí hay un ejemplo de trabajo completo en CodeSandbox .

Entonces, después de todo, ¿qué usar, Context o Redux?

Ahora que has visto ambos caminos, ¿cuál vale la pena usar? Sé que solo quieres escuchar la respuesta a esta pregunta, pero tengo que responder: "depende de ti".

Depende de qué tan grande sea su aplicación ahora o qué tan rápido crezca. ¿Cuántas personas trabajarán en él, solo usted o un equipo grande? ¿Qué experiencia tiene usted o su equipo en trabajar con los conceptos funcionales en los que se basa Redux (como la inmutabilidad y las características puras).

Un error fatal que impregna todo el ecosistema de JavaScript es la idea de la competencia . Existe la idea de que cada opción es un juego de suma cero: si usa la biblioteca A, no debe usar su biblioteca competidora B. Que cuando salga una nueva biblioteca que sea mejor que la anterior, debería desplazar a la existente. Que todo debe ser / o que debes elegir lo más nuevo y lo mejor, o ser relegado a un segundo plano con desarrolladores del pasado.

El mejor enfoque es mirar esta maravillosa opción con un ejemplo, un conjunto de herramientas. Es como elegir entre usar un destornillador o un destornillador potente. Para el 80% de los casos, un destornillador hará el trabajo más fácil y más rápido que un destornillador. Pero para el otro 20%, un destornillador sería la mejor opción (no hay suficiente espacio o el artículo es delgado). Cuando compré un destornillador, no tiré inmediatamente el destornillador, no lo reemplazó, sino que simplemente me dio otra opción. Otra forma de resolver el problema.

El contexto no "reemplaza" Redux, no más que React "reemplaza" Angular o jQuery. Demonios, todavía uso jQuery cuando necesito hacer algo rápidamente. A veces sigo usando plantillas EJS del lado del servidor en lugar de implementar una aplicación React. A veces, Reaccionar es más de lo que necesitas para completar una tarea. Lo mismo vale para Redux.

Hoy, si Redux es más de lo que necesita, puede usar el contexto.

Learning React puede ser difícil: ¡hay tantas bibliotecas y herramientas!

Mi consejo Ignórelos a todos :)

Para un tutorial paso a paso, lea mi libro , Pure React .

Source: https://habr.com/ru/post/es419449/


All Articles