Presentamos Immer: la inmutabilidad de la manera más fácil

Las estructuras de datos inmutables y estructuralmente compartidas son un gran paradigma para almacenar el estado. Especialmente cuando se combina con una arquitectura de abastecimiento de eventos. Sin embargo, hay un costo a pagar. En un lenguaje como JavaScript donde la inmutabilidad no está incorporada en el lenguaje, producir un nuevo estado con respecto al anterior es una tarea aburrida y caldeable. Para probar el punto: la página Redux-ecosystem-links enumera 67 (!) Paquetes para ayudarlo a manejar estructuras de datos inmutables en Redux.

Y todavía; la mayoría de ellos no resuelve el problema de raíz: falta de soporte de idiomas. Por ejemplo, cuando la update-in es un concepto elegante en un lenguaje como ClojureScript, las contrapartes de JavaScript básicamente se basarán en rutas de cadena feas. Los cuales son propensos a errores, difíciles de verificar y requieren aprender de memoria otro conjunto de funciones de API para ser proeficientes.

Entonces, ¿qué pasa si dejamos de luchar contra el lenguaje y lo aceptamos? Sin renunciar a la elegancia proporcionada por las estructuras de datos persistentes. Eso es exactamente lo que hace immer .

Sugerencia: si no le gusta leer, también puede ver el tutorial de egghead para immer

Productores

Immer trabaja escribiendo productores, y el productor más simple posible se ve así:

Un productor mínimo (vacío) devolverá el estado original

La función producir toma dos argumentos. El estado actual y una función de productor . El estado actual determina nuestro punto de partida y el productor expresa lo que debe sucederle. La función de productor recibe un argumento, el borrador , que es un proxy del estado actual que pasó. Cualquier modificación que realice en el borrador será registrada y utilizada para producir nextState . El estado actual no se tocará durante este proceso.

Debido a que immer utiliza el intercambio estructural , y nuestro ejemplo de productor anterior no modificó nada, el siguiente estado anterior es simplemente el estado con el que comenzamos.

Echemos un vistazo a lo que sucede cuando comenzamos a modificar el borrador en nuestro productor. Tenga en cuenta que la función de productor no devuelve nada, lo único que importa son los cambios que hacemos.

Un verdadero productor. Todos los cambios en el borrador se reflejan en el siguiente estado, que comparte estructuralmente los elementos intactos con el estado anterior

Aquí realmente vemos productos en acción. Creamos un nuevo árbol de estado, que contiene un elemento adicional de tareas pendientes. Además, se cambió el estado del segundo todo. Aquí están los cambios que aplicamos al borrador, y se reflejan bien en el próximo estado resultante.

Pero hay más. Las últimas declaraciones en la lista muestran muy bien que las partes del estado que se modificaron en el borrador han resultado en nuevos objetos. Sin embargo, las partes inalteradas se comparten estructuralmente con el estado anterior. El primer todo en este caso.

Un reductor con un productor

Ahora aprendimos los conceptos básicos de producir un nuevo estado. Aprovechemos esto en un reductor Redux ejemplar. La siguiente idea se basa en el ejemplo oficial del carrito de compras y carga un grupo de (posiblemente) nuevos productos en el estado. Los productos se reciben como una matriz, se transforman usando reducir y luego se almacenan en un mapa con su identificación como clave.

Un reductor Redux típico

La parte de la boilerplaty aquí es:

  1. Tenemos que construir un nuevo objeto de estado, en el que se conserve el estado base y se mezcle el nuevo mapa de productos. No es tan malo en este caso simple, pero este proceso debe repetirse para cada acción y en cada nivel en el que queremos modificar algo
  2. Tenemos que asegurarnos de devolver el estado existente si el reductor no hace nada

Con Immer, solo tenemos que razonar sobre los cambios que queremos hacer con relación al estado actual. Sin necesidad de hacer el esfuerzo de producir realmente el siguiente estado. Entonces, cuando usamos producir en el reductor, nuestro código simplemente se convierte en:

Simplificando el reductor usando Immer

¿ RECEIVE_PRODUCTS lo fácil que es captar lo que RECEIVE_PRODUCTS está haciendo realmente? El ruido ha sido eliminado en gran parte. También tenga en cuenta que no manejamos el caso predeterminado. No cambiar el borrador simplemente equivale a devolver el estado base. Tanto el reductor original como el nuevo se comportan exactamente igual.

Sin ataduras

La idea de producir el próximo estado inmutable mediante la modificación de un borrador provisional no es nueva. Por ejemplo, inmutableJS proporciona un mecanismo similar: withMutations . Sin embargo, la gran ventaja de Immer es que no tiene que aprender (ni cargar) una biblioteca completamente nueva para sus estructuras de datos. Immer opera en arreglos y objetos JavaScript normales.

Las ventajas van incluso más allá. Para reducir la repetición, ImmutableJS y muchos otros le permiten expresar actualizaciones profundas (y muchas otras operaciones) con métodos dedicados. Sin embargo, estas rutas son cadenas sin formato y no pueden ser verificadas por los revisores de tipos. Son bastante propensos a errores. En la siguiente lista, por ejemplo, el tipo de list no se puede inferir en el caso ImmutableJS. Otras bibliotecas llevan esto incluso un paso más allá e incluso alteran sus propias DSL en estas consultas de ruta, habilitando comandos más complejos como los empalmes. A costa de introducir un mini lenguaje en el idioma.

Immer sigue escribiendo con actualizaciones profundas

Immer no sufre de nada de eso; funciona en estructuras de JavaScript integradas. Perfectamente entendido por cualquier tipo de corrector. Y la modificación de los datos se realiza a través de API con las que ya está familiarizado; los integrados en el lenguaje.

Congelación automática

Otra característica interesante ( ? ) de Immer es que congelará automáticamente cualquier estructura de datos que haya creado utilizando produce . (En modo de desarrollo). Para que pueda obtener datos verdaderamente inmutables. Donde congelar todo el estado sería bastante costoso, el hecho de que Immer pueda congelar las partes modificadas lo hace bastante eficiente. Y, si todo su estado es producido por funciones de produce , el resultado efectivo será que su estado entero siempre se congelará. Lo que significa que obtendrá una excepción cuando intente modificar el estado de alguna manera.

Zurra

De acuerdo. Una última característica: hasta ahora siempre hemos llamado produce con dos argumentos, el baseState y una función de producer . Sin embargo, en algunos casos, puede ser conveniente usar una aplicación parcial. Es posible llamar a produce solo con la función de productor. Esto creará una nueva función que ejecutará al productor cuando se pase en un estado. Esta nueva función también acepta una cantidad arbitraria de argumentos adicionales y los pasa al productor.

No se preocupe si no puede analizar las últimas oraciones. Lo que se reduce a esto es que puede reducir aún más la repetición de los reductores al aprovechar el currying:

Un productor curry (también vea el listado anterior para comparar)