Por qué Context no es una herramienta de "gestión estatal"





TL; DR



¿Son lo mismo Context y Redux?


No. Son herramientas diferentes que hacen cosas diferentes y se utilizan para diferentes propósitos.



¿Es el contexto una herramienta para la "gestión estatal"?


No. El contexto es una forma de inyección de dependencia. Es un mecanismo de transporte que no controla nada. Cualquier "gestión de estado" se realiza manualmente, normalmente utilizando los ganchos useState () / useReducer ().



¿Son Context y useReducer () un reemplazo para Redux?


No. Son algo similares y se superponen parcialmente, pero difieren mucho en términos de capacidades.



¿Cuándo debería usar el contexto?


Cuando desea que algunos datos estén disponibles para varios componentes, pero no desea pasar esos datos como accesorios en cada nivel del árbol de componentes.



¿Cuándo debería usar Context y useReducer ()?


Cuando necesita administrar el estado de un componente moderadamente complejo en una parte específica de su aplicación.



¿Cuándo debería utilizar Redux?


Redux es más útil cuando:



  • Gran cantidad de componentes con estado que utilizan los mismos datos
  • El estado de la aplicación se actualiza con frecuencia
  • Lógica compleja para actualizar el estado
  • La aplicación tiene una base de código de tamaño mediano a grande y muchas personas están trabajando en ella
  • Desea saber cuándo, por qué y cómo se actualiza el estado de la aplicación y poder visualizar esos cambios.
  • Necesita capacidades más potentes para manejar efectos secundarios, estabilidad (persistencia) y serialización de datos.




Comprender el contexto y Redux



Para utilizar la herramienta correctamente, es fundamental comprender:



  • Para qué sirve
  • Que tareas resuelve
  • Cuándo y por qué fue creado


También es importante comprender qué problemas está tratando de resolver en su aplicación y usar las herramientas que mejor los resuelvan, no porque alguien le haya dicho que los use, y no porque sean populares, sino porque funcionan mejor para usted en un situación particular.



La confusión en torno a Context y Redux se debe principalmente a una mala comprensión de para qué están destinadas estas herramientas y qué tareas resuelven. Por tanto, antes de hablar de cuándo deben utilizarse, es necesario determinar qué son y qué problemas resuelven.



¿Qué es el contexto?



Comencemos por definir el contexto a partir de la documentación oficial :



“El contexto te permite pasar datos a través del árbol de componentes sin tener que pasar accesorios en niveles intermedios.



En una aplicación típica de React, los datos se pasan de arriba hacia abajo (de padre a hijo) utilizando accesorios. Sin embargo, este método puede ser excesivo para algunos tipos de accesorios (por ejemplo, idioma seleccionado, tema de interfaz) que deben pasarse a muchos componentes de una aplicación. El contexto proporciona una forma de distribuir dichos datos entre los componentes sin tener que pasar explícitamente props en cada nivel del árbol de componentes ".



Tenga en cuenta que esta definición no dice una palabra sobre "gestión", solo sobre "transferencia" y "distribución".



La API de contexto actual (React.createContext ()) se introdujo por primera vez en React 16.3 como un reemplazo de la API heredada disponible en versiones anteriores de React, pero con varios defectos de diseño. Uno de los principales problemas era que las actualizaciones de los valores pasados ​​a través del contexto podían "bloquearse" si el componente saltaba la representación a través de shouldComponentUpdate (). Dado que muchos componentes recurrieron a shouldComponentUpdate () con fines de optimización, pasar datos a través del contexto se volvió inútil. createContext () fue diseñado para abordar este problema, por lo que cualquier actualización del valor se reflejará en los componentes secundarios, incluso si el componente intermedio omite la representación.



Usando contexto


El uso de contexto en una aplicación supone lo siguiente:



  • Llame a const MyContext = React.createContext () para instanciar el objeto de contexto
  • En el componente principal, renderice & ltMyContext.Provider value = {someValue}>. Esto pone algunos datos en contexto. Estos datos pueden ser cualquier cosa: cadena, número, objeto, matriz, instancia de clase, controlador de eventos, etc.
  • Obtenga el valor de contexto en cualquier componente dentro del proveedor llamando a const theContextValue = useContext (MyContext)


Cuando se actualiza el componente principal y se pasa la nueva referencia como valor de proveedor, cualquier componente que "consuma" el contexto se verá obligado a actualizarse.



Normalmente, el valor de contexto es el estado del componente:



import { createContext } from 'react'

export const MyContext = createContext()

export function ParentComponent({ children }) {
  const [counter, setCounter] = useState(0)

  return (
    <MyContext.Provider value={[counter, setCounter]}>
      {children}
    </MyContext.Provider>
  )
}

      
      





El componente hijo puede llamar al gancho useContext () y leer el valor de contexto:



import { useContext } from 'react'
import { MyContext } from './MyContext'

export function NestedChildComponent() {
  const [counter, setCounter] = useContext(MyContext)

  // ...
}

      
      







Podemos ver que el contexto realmente no controla nada. En cambio, es una especie de tubería. Pones datos al principio (parte superior) del túnel usando <MyContext.Provider>, luego estos datos se reducen hasta que el componente los solicita usando useContext (MyContext).



Por lo tanto, el propósito principal del contexto es evitar la perforación con puntal. En lugar de pasar datos como accesorios en cada nivel del árbol de componentes, cualquier componente anidado en <MyContext.Provider> puede acceder a él a través de useContext (MyContext). Esto elimina la necesidad de escribir código para implementar la lógica de paso de prop.



Conceptualmente, esta es una forma de inyección de dependencia... Sabemos que el niño necesita datos de cierto tipo, pero no intenta crear o configurar estos datos por sí solo. En cambio, depende de algún antepasado para pasar estos datos en tiempo de ejecución.



¿Qué es Redux?



Esto es lo que dice la definición básica de Redux :



“Redux es un patrón de diseño y una biblioteca para administrar y actualizar el estado de la aplicación usando eventos llamados operaciones. Redux actúa como un repositorio centralizado del estado de la aplicación, siguiendo reglas para garantizar actualizaciones de estado predecibles.



Redux le permite administrar el estado "global" - estado que se canaliza a múltiples partes de su aplicación.



Los patrones y herramientas proporcionados por Redux facilitan la determinación de dónde, cuándo, por qué y cómo se actualizó el estado y cómo respondió la aplicación a ese cambio ".



Tenga en cuenta que esta descripción indica:



  • Administración del Estado
  • El propósito de Redux es determinar por qué y cómo ocurrió un cambio de estado


Redux fue originalmente una implementación de la "arquitectura Flux" , un patrón de diseño desarrollado por Facebook en 2014, un año después del lanzamiento de React. Desde la llegada de Flux, la comunidad ha desarrollado muchas bibliotecas que implementan este concepto de diferentes formas. Redux apareció en 2015 y rápidamente se convirtió en el ganador de esta competencia gracias a su cuidadoso diseño, resolviendo problemas comunes y excelente compatibilidad con React.



Arquitectónicamente, Redux usa enfáticamente los principios de la programación funcional, lo que le permite escribir código en forma de "reductores" predecibles y separar la idea de "lo que sucedió" de la lógica que define "cómo se actualiza el estado cuando esto evento ocurre." Redux también usa middleware como una forma de ampliar las capacidades de la tienda, incluido el manejo de efectos secundarios .



Redux también proporciona herramientas de desarrollo para explorar el historial de operaciones y cambios de estado a lo largo del tiempo.



Redux y React


Redux en sí es independiente de la interfaz de usuario: puede usarlo con cualquier capa de vista (React, Vue, Angular, vanilla JS, etc.) o sin interfaz de usuario.



Sin embargo, la mayoría de las veces, Redux se usa junto con React. La biblioteca React Redux es la capa de enlace de la interfaz de usuario oficial que permite que los componentes de React interactúen con la tienda Redux, recuperando valores del estado de Redux e iniciando operaciones. React-Redux usa el contexto internamente. Sin embargo, debe tenerse en cuenta que React-Redux pasa a través del contexto una instancia de la tienda Redux, ¡no el valor de estado actual!Este es un ejemplo del uso de contexto para la inyección de dependencia. Sabemos que nuestros componentes conectados a Redux necesitan interactuar con la tienda Redux, pero no sabemos o no nos importa cuál es esa tienda cuando definimos el componente. La tienda real de Redux se inyecta en el árbol en tiempo de ejecución utilizando el componente <Provider> proporcionado por React-Redux.



Por lo tanto, React-Redux también se puede utilizar para evitar "perforaciones" (debido al uso interno del contexto). En lugar de pasar explícitamente el nuevo valor a través de <MyContext.Provider>, podemos poner estos datos en la tienda de Redux y luego recuperarlos en el componente deseado.



Propósito y casos de uso de (React-) Redux


El objetivo principal de Redux según la documentación oficial:



"Los patrones y herramientas proporcionados por Redux facilitan la comprensión de cuándo, dónde, por qué y cómo ocurrió un cambio de estado, y cómo reaccionó la aplicación".



Hay varias razones más para usar Redux. Una de estas razones es evitar la "perforación".



Otros casos de uso:



  • Separación completa de la lógica de gestión del estado y la capa de interfaz de usuario
  • Distribución de la lógica de gestión del estado entre diferentes capas de la interfaz de usuario (por ejemplo, en el proceso de traducir una aplicación de AngularJS a React)
  • Uso de middleware de Redux para agregar lógica adicional al inicializar operaciones
  • Posibilidad de guardar partes del estado de Redux
  • Posibilidad de recibir informes de errores que otros desarrolladores pueden reproducir.
  • Capacidad para depurar rápidamente la lógica y la interfaz de usuario durante el desarrollo


Dan Abramov enumeró estos casos en su artículo de 2016 Why You May Not Need Redux .



¿Por qué el contexto no es una herramienta para la "gestión estatal"?



El estado es cualquier dato que describe el comportamiento de una aplicación . Podemos clasificar el estado en categorías como estado del servidor, estado de comunicación y estado local si queremos, pero el aspecto clave es almacenar, leer, actualizar y usar datos.



David Khourshid, autor de la biblioteca XState y especialista en administración estatal, señaló en uno de sus tweets que:



"La administración estatal se trata de cambiar de estado con el tiempo".



Así, podemos decir que "gestión estatal" significa lo siguiente:



  • Almacenar el valor inicial
  • Obteniendo el valor actual
  • Actualizar un valor


Además, suele haber una forma de recibir una notificación cuando el valor del estado actual ha cambiado.



Los hooks de React useState () y useReducer () son excelentes ejemplos de administración de estados. Con estos ganchos podemos:



  • Almacene el valor inicial llamando a un gancho
  • Obtenga el valor actual también llamando al gancho
  • Actualice el valor llamando a setState () o dispatch (), respectivamente
  • Tenga en cuenta las actualizaciones de estado al volver a renderizar el componente


Redux y MobX también le permiten administrar el estado:



  • Redux almacena el valor inicial llamando al reductor raíz, permite leer el valor actual con store.getState (), actualizar el valor con store.dispatch (acción) y recibir notificaciones de actualización de estado a través de store.subscribe (oyente)
  • MobX conserva un valor inicial asignando un valor a un campo de clase de almacenamiento, permite que el valor actual sea leído y actualizado a través de los campos de almacenamiento y recibe notificaciones de actualización de estado usando los métodos autorun () y computed ()


Las herramientas de administración de estado incluyen incluso herramientas de caché del servidor como React-Query, SWR, Apollo y Urql: almacenan un valor inicial basado en los datos obtenidos, devuelven el valor actual mediante enlaces y permiten actualizar los valores a través de "mutaciones del servidor" y notificar los cambios al volver a renderizar el componente.



React Context no cumple con los criterios mencionados. Por tanto no es una herramienta de gestión estatal


Como se señaló anteriormente, el contexto en sí mismo no almacena nada. El componente principal, que representa <MyContext.Provider>, es responsable de pasar el valor, que generalmente depende del estado del componente. La "administración de estado" real proviene de los ganchos useState () / useReducer ().



David Khourshid también señala:



“El contexto es cómo se comparte el estado existente entre los componentes. El contexto no hace nada con el estado ".



Y en un tweet posterior ,



"Creo que el contexto es como accesorios ocultos en ese estado abstracto".



Todo lo que hace ese contexto es evitar "perforar".



Comparación de contexto y Redux



Comparemos las capacidades del contexto y React + Redux:



  • Contexto
    • No almacena nada y no gestiona nada
    • Solo funciona en componentes React
    • Pasa por debajo de un valor simple (único), que puede ser cualquier cosa (primitivo, objeto, clase, etc.)
    • Leamos este simple significado
    • Puede utilizarse para evitar "perforaciones"
    • Muestra el valor actual de los componentes Proveedor y Consumidor en las herramientas para desarrolladores, pero no muestra el historial de cambios en este valor.
    • Actualiza los componentes consumidores cuando cambia el valor, pero no permite que se omita la actualización.
    • No proporciona ningún mecanismo para manejar los efectos secundarios, solo es responsable de la renderización


  • Reaccionar + Redux
    • Almacena y administra un valor simple (generalmente un objeto)
    • Funciona con cualquier interfaz de usuario y fuera de los componentes de React
    • Leamos este simple significado
    • Puede utilizarse para evitar "perforaciones"
    • Puede actualizar el valor inicializando operaciones y ejecutando reductores
    • Las herramientas de desarrollo muestran el historial de inicialización de operaciones y cambios de estado
    • Proporciona la capacidad de usar middleware para manejar efectos secundarios.
    • Permite que los componentes se suscriban para almacenar actualizaciones, recuperar partes específicas del estado de la tienda y controlar la reproducción de componentes.




Obviamente, estas son herramientas completamente diferentes con diferentes capacidades. El único punto de intersección entre ellos es evitar la "perforación".



Contexto y useReducer ()



Uno de los problemas con la discusión "Contexto vs. Redux" es que la gente a menudo quiere decir "Yo uso useReducer () para administrar el estado y el contexto para pasar valor". Pero en cambio, simplemente dicen: "Estoy usando el contexto". Este, en mi opinión, es el principal motivo de la confusión que contribuye al mantenimiento del mito de que el contexto "controla al Estado".



Considere la combinación de Context + useReducer (). Sí, esta combinación se parece mucho a Redux + React-Redux. Ambas combinaciones tienen:



  • Valor almacenado
  • Función reductora
  • Capacidad para inicializar operaciones
  • La capacidad de pasar un valor y leerlo en componentes anidados.


Sin embargo, todavía existen algunas diferencias importantes entre ellos, que se manifiestan en sus capacidades y comportamiento. He notado estas diferencias en React, Redux y Context Behavior y La guía (casi) completa para renderizar en React . En resumen, se puede señalar lo siguiente:



  • Context + useReducer () se basa en pasar el valor actual a través del contexto. React-Redux pasa la instancia actual de la tienda Redux a través del contexto
  • Esto significa que cuando useReducer () produce un nuevo valor, todos los componentes suscritos al contexto se ven obligados a volver a dibujar, incluso si solo usan algunos de los datos. Esto puede dar lugar a problemas de rendimiento según el tamaño del valor de estado, el número de componentes firmados y la frecuencia de reproducción. Con React-Redux, los componentes pueden suscribirse a una parte específica del valor de la tienda y solo volver a dibujar cuando esa parte cambia


Hay otras diferencias importantes:



  • Context + useReducer () son capacidades integradas de React y no se pueden usar fuera de él. La tienda Redux es independiente de la interfaz de usuario, por lo que se puede usar por separado de React
  • React DevTools , . Redux DevTools , ( , type and payload),
  • useReducer() middleware. useEffect() useReducer(), useReducer() middleware, Redux middleware


Esto es lo que dijo Sebastian Markbage (Arquitecto del equipo de React Core) sobre el uso del contexto :



“Mi opinión personal es que el nuevo contexto está listo para usarse en actualizaciones improbables de baja frecuencia (como la localización o el tema). También se puede utilizar en todos los casos en los que se utilizó el contexto anterior, es decir, para valores estáticos con posterior distribución de la actualización por suscripción. No está listo para ser utilizado como reemplazo de distribuidores estatales similares a Flux ".



Hay muchos artículos en la web que recomiendan configurar múltiples contextos separados para diferentes partes del estado, evitando repeticiones innecesarias y resolviendo problemas de alcance. Algunas de las publicaciones también sugieren agregar sus propios "componentes contextuales" , lo que requiere una combinación de React.memo (), useMemo (), y dividir cuidadosamente el código en dos contextos para cada parte de la aplicación (uno para datos, otro para funciones de actualización). Por supuesto, puede escribir código de esta manera, pero en este caso está reinventando React-Redux.



Entonces, si bien Context + useReducer () es una alternativa liviana a Redux + React-Redux en una primera aproximación ... estas combinaciones no son idénticas, context + useReducer () no puede reemplazar completamente a Redux.



Elegir la herramienta adecuada



Para elegir la herramienta adecuada, es muy importante comprender qué tareas resuelve la herramienta, así como a qué tareas se enfrenta.



Resumen de casos de uso



  • Contexto

    • Transferencia de datos a componentes anidados sin "perforar"


  • useReducer ()

    • Controlar el estado de un componente complejo mediante una función reductora


  • Contexto + useReducer ()

    • Administrar el estado de un componente complejo mediante una función reductora y transferir el estado a componentes anidados sin "perforar"


  • Redux

    • Controlando un estado muy complejo con funciones reductoras
    • Trazabilidad de cuándo, por qué y cómo cambió el estado con el tiempo.
    • Deseo de aislar completamente la lógica de gestión del estado de la capa de interfaz de usuario
    • Distribución de la lógica de gestión del estado entre diferentes capas de la interfaz de usuario
    • Usar las capacidades del middleware para implementar lógica adicional al inicializar operaciones
    • La capacidad de salvar ciertas partes del estado.
    • Capacidad para recibir informes de errores reproducibles
    • Capacidad para depurar rápidamente la lógica y la interfaz de usuario durante el desarrollo


  • Redux + React-Redux

    • Todos los casos de uso de Redux + la capacidad de los componentes de React para interactuar con la tienda de Redux




Una vez más: ¡las herramientas nombradas resuelven diferentes problemas!



Recomendaciones



Entonces, ¿cómo decides qué usar?



Para hacer esto, solo necesita determinar qué herramienta resuelve mejor los problemas de su aplicación.



  • Si solo desea evitar "perforar", use el contexto
  • , , + useReducer()
  • , , .., Redux + React-Redux


Creo que si su aplicación tiene 2-3 contextos para administrar el estado, entonces debería cambiar a Redux.



A menudo escuchará que "usar Redux implica escribir mucho código repetitivo", sin embargo, "Redux moderno" hace que sea mucho más fácil de aprender y usar. El kit de herramientas oficial de Redux resuelve el problema de las plantillas, y los ganchos de React-Redux facilitan el uso de Redux en los componentes de React.



Por supuesto, agregar RTK y React-Redux como dependencias aumenta el paquete de aplicaciones sobre el contexto + useReducer (), que están integrados. Pero las ventajas de este enfoque cubren las desventajas: mejor seguimiento del estado, lógica más simple y predecible, optimización de representación mejorada de componentes.



También es importante tener en cuenta que uno no excluye al otro: puede usar Redux, Context y useReducer () juntos. Recomendamos almacenar el estado "global" en Redux y el estado local en los componentes, y tenga cuidado al determinar qué parte de la aplicación debe almacenarse en Redux y cuál en los componentes.... Entonces puede usar Redux para almacenar el estado global, Context + useReducer () para almacenar el estado local y Context para almacenar valores estáticos, todo simultáneamente en la misma aplicación.



Una vez más, no estoy argumentando que todo el estado de la aplicación deba almacenarse en Redux, o que Redux sea siempre la mejor solución. Mi punto es que Redux es una buena opción, hay muchas razones para usar Redux y las tarifas para usarlo no son tan altas como muchos piensan.



Finalmente, el contexto y Redux no son únicos. Hay muchas otras herramientas que abordan otros aspectos de la gestión estatal. MobX es una solución popular que usa OOP y observables para actualizar automáticamente las dependencias. Otros enfoques para la renovación del estado incluyen Jotai, Recoil y Zustand. Las bibliotecas de datos como React Query, SWR, Apollo y Urql proporcionan abstracciones que facilitan el uso de patrones comunes para trabajar con el estado en caché del servidor ( pronto aparecerá una biblioteca similar ( RTK Query ) para Redux Toolkit).



Espero que este artículo le haya ayudado a comprender la diferencia entre el contexto y Redux, y qué herramienta debe usarse y cuándo. Gracias por su atención.



All Articles