Usando Immutable.JS con Redux#

Tabla de Contenidos#

  • ¿Por qué debería usar una librería enfocada a la inmutabilidad como Immutable.JS?
  • ¿Por qué debería elegir Immutable.JS como una librería inmutable?
  • ¿Cuáles son los problemas de usar Immutable.JS?
  • ¿Vale la pena el esfuerzo de Immutable.JS?
  • ¿Cuáles son algunas de las mejores prácticas para usar Immutable.JS con Redux?

¿Por qué debería usar una biblioteca centrada en lo inmutable como Immutable.JS?

Las bibliotecas centradas en lo inmutable como Immutable.JS han sido diseñadas para superar los problemas de inmutabilidad inherentes a JavaScript, proporcionando todos los beneficios de la inmutabilidad con el rendimiento que su aplicación requiere.

Si decide utilizar una biblioteca de este tipo, o seguir con JavaScript, depende de lo cómodo que se sienta con la adición de otra dependencia a su aplicación, o de lo seguro que esté de poder evitar las trampas inherentes al enfoque de JavaScript para la inmutabilidad.

Cualquiera que sea la opción que elija, asegúrese de estar familiarizado con los conceptos de inmutabilidad, efectos secundarios y mutación. En particular, asegúrese de tener una comprensión profunda de lo que JavaScript hace al actualizar y copiar valores con el fin de protegerse contra las mutaciones accidentales que degradarán el rendimiento de su aplicación, o romperlo por completo.

Más información#

Documentación

  • Recetas: inmutabilidad, efectos secundarios y mutación

Artículos

  • Introducción a Immutable.js y conceptos de programación funcional
  • Pros y contras del uso de la inmutabilidad con React.js

¿Por qué debería elegir Immutable.JS como una biblioteca inmutable?#

Immutable.JS fue diseñado para proporcionar inmutabilidad de una manera performante en un esfuerzo por superar las limitaciones de la inmutabilidad con JavaScript. Sus principales ventajas incluyen:

Inmutabilidad garantizada#

Los datos encapsulados en un objeto Immutable.JS nunca se mutan. Siempre se devuelve una nueva copia. Esto contrasta con JavaScript, en el que algunas operaciones no mutan sus datos (por ejemplo, algunos métodos de Array, incluyendo map, filter, concat, forEach, etc.), pero algunos lo hacen (pop, push, splice, etc. de Array).

Rich API#

Immutable.JS proporciona un rico conjunto de objetos inmutables para encapsular sus datos (e.g. Mapas, Listas, Conjuntos, Registros, etc.), y un amplio conjunto de métodos para manipularlos, incluyendo métodos para ordenar, filtrar y agrupar los datos, invertirlos, aplanarlos y crear subconjuntos.

Rendimiento#

Immutable.JS hace mucho trabajo detrás de las escenas para optimizar el rendimiento. Esta es la clave de su poder, ya que el uso de estructuras de datos inmutables puede implicar una gran cantidad de copias costosas. En particular, la manipulación inmutable de conjuntos de datos grandes y complejos, como un árbol de estado Redux anidado, puede generar muchas copias intermedias de objetos, que consumen memoria y ralentizan el rendimiento mientras el recolector de basura del navegador lucha por limpiar las cosas.

Immutable.JS evita esto compartiendo inteligentemente estructuras de datos bajo la superficie, minimizando la necesidad de copiar datos. También permite llevar a cabo complejas cadenas de operaciones sin crear innecesarios (y costosos) datos intermedios clonados que serán rápidamente desechados.

Nunca ves esto, por supuesto – los datos que das a un objeto Immutable.JS nunca se mutan. Más bien, son los datos intermedios generados dentro de Immutable.JS a partir de una secuencia encadenada de llamadas a métodos los que están libres de ser mutados. Por lo tanto, usted obtiene todos los beneficios de las estructuras de datos inmutables con ninguno (o muy poco) de los potenciales golpes de rendimiento.

Más información#

Artículos

  • Immutable.js, estructuras de datos persistentes y compartición estructural
  • PDF: JavaScript Immutability – Don’t go changing

Libraries

  • Immutable.js

¿Cuáles son los problemas con el uso de Immutable.JS?#

Aunque potente, Immutable.JS necesita ser utilizado con cuidado, ya que viene con problemas propios. Tenga en cuenta, sin embargo, que todos estos problemas se pueden superar fácilmente con una codificación cuidadosa.

Difícil de interoperar con#

JavaScript no proporciona estructuras de datos inmutables. Como tal, para que Immutable.JS proporcione sus garantías de inmutabilidad, sus datos deben estar encapsulados dentro de un objeto Immutable.JS (como un Map o un List, etc.). Una vez que está contenida de esta manera, es difícil que esos datos interoperen con otros objetos JavaScript simples.

Por ejemplo, ya no podrá hacer referencia a las propiedades de un objeto a través de la notación estándar de puntos o corchetes de JavaScript. En su lugar, debe hacer referencia a ellas a través de los métodos get() o getIn() de Immutable.JS, que utilizan una sintaxis incómoda que accede a las propiedades a través de una matriz de cadenas, cada una de las cuales representa una clave de propiedad.

Por ejemplo, en lugar de myObj.prop1.prop2.prop3, utilizaría myImmutableMap.getIn().

Esto hace que sea incómodo interoperar no sólo con tu propio código, sino también con otras bibliotecas, como lodash o ramda, que esperan objetos JavaScript planos.

Nota que los objetos Immutable.JS tienen un método toJS(), que devuelve los datos como una estructura de datos JavaScript simple, pero este método es extremadamente lento, y su uso extensivo anulará los beneficios de rendimiento que Immutable.JS proporciona

Una vez utilizado, Immutable.JS se extenderá por toda su base de código#

Una vez que encapsule sus datos con Immutable.JS, tienes que usar los accesores de propiedad get() o getIn() de Immutable.JS para acceder a ellos.

Esto tiene el efecto de propagar Immutable.JS a través de toda tu base de código, incluyendo potencialmente tus componentes, donde puedes preferir no tener tales dependencias externas. Toda su base de código debe saber qué es, y qué no es, un objeto Immutable.JS. También hace que la eliminación de Immutable.JS de su aplicación sea difícil en el futuro, si alguna vez lo necesita.

Este problema se puede evitar desacoplando la lógica de su aplicación de sus estructuras de datos, como se indica en la sección de mejores prácticas a continuación.

No hay operadores de desestructuración o propagación#

Debido a que debe acceder a sus datos a través de Immutable.JS, ya no puede utilizar el operador de desestructuración de JavaScript (o el operador de propagación de objetos propuesto), lo que hace que su código sea más verboso.

No es adecuado para los valores pequeños que cambian a menudo#

Imutable.JS funciona mejor para las colecciones de datos, y cuanto más grande, mejor. Puede ser lento cuando sus datos comprenden un montón de pequeños y simples objetos JavaScript, con cada uno de ellos comprendiendo unas pocas claves de valores primitivos.

Nótese, sin embargo, que esto no se aplica al árbol de estado de Redux, que (normalmente) se representa como una gran colección de datos.

Difícil de depurar#

Los objetos de Immutable.JS, como Map, List, etc., pueden ser difíciles de depurar, ya que la inspección de un objeto de este tipo revelará toda una jerarquía anidada de propiedades específicas de Immutable.JS que no le interesan, mientras que sus datos reales que le interesan están encapsulados a varias capas de profundidad.

Para resolver este problema, utilice una extensión del navegador como Immutable.js Object Formatter, que hace aflorar sus datos en Chrome Dev Tools, y oculta las propiedades de Immutable.JS al inspeccionar sus datos.

Rompe las referencias a los objetos, causando un bajo rendimiento#

Una de las ventajas clave de la inmutabilidad es que permite la comprobación de la igualdad superficial, lo que mejora drásticamente el rendimiento.

Si dos variables diferentes hacen referencia al mismo objeto inmutable, entonces una simple comprobación de la igualdad de las dos variables es suficiente para determinar que son iguales, y que el objeto al que ambos hacen referencia no se cambia. La comprobación de la igualdad nunca tiene que comprobar los valores de cualquiera de las propiedades del objeto, ya que es, por supuesto, inmutable.

Sin embargo, la comprobación superficial no funcionará si sus datos encapsulados dentro de un objeto Immutable.JS es en sí mismo un objeto. Esto se debe a que el método toJS() de Immutable.JS, que devuelve los datos contenidos dentro de un objeto Immutable.JS como un valor JavaScript, creará un nuevo objeto cada vez que sea llamado, y así romperá la referencia con los datos encapsulados.

En consecuencia, llamar a toJS() dos veces, por ejemplo, y asignar el resultado a dos variables diferentes hará que falle una comprobación de igualdad en esas dos variables, aunque los valores del objeto en sí no hayan cambiado.

Esto es un problema particular si se utiliza toJS() en la función mapStateToProps de un componente envuelto, ya que React-Redux compara superficialmente cada valor en el objeto props devuelto. Por ejemplo, el valor referenciado por la proposición todos devuelta desde mapStateToProps siempre será un objeto diferente, y por lo tanto fallará una comprobación de igualdad superficial.

// EVITAR .toJS() en mapStateToProps
function mapStateToProps(state) {
return {
todos: state.get(‘todos’).toJS() // Siempre un nuevo objeto
}
}

Copiar

Cuando la comprobación superficial falla, React-Redux hará que el componente se vuelva a renderizar. El uso detoJS()enmapStateToPropsde esta manera, por lo tanto, siempre causará que el componente se vuelva a renderizar, incluso si el valor nunca cambia, impactando fuertemente en el rendimiento.

Esto se puede evitar mediante el uso de toJS() en un componente de orden superior, como se discute en la sección de mejores prácticas a continuación.

Más información#

Artículos

  • Immutable.js, estructuras de datos persistentes y compartición estructural
  • Estructuras de datos inmutables y JavaScript
  • React.js pure render performance anti-pattern
  • Building Efficient UI with React and Redux

Chrome Extension

  • Immutable Object Formatter

¿Vale la pena usar Immutable.JS? Hay varias compensaciones y opiniones a considerar, pero hay muchas buenas razones para usar Immutable.JS. No subestime la dificultad de tratar de localizar una propiedad de su árbol de estado que ha sido mutada inadvertidamente.

Los componentes se volverán a renderizar cuando no deban, y se negarán a renderizar cuando deban, y el seguimiento del error que causa el problema de renderización es difícil, ya que el componente que se renderiza incorrectamente no es necesariamente aquel cuyas propiedades están siendo mutadas accidentalmente.

Este problema es causado predominantemente por la devolución de un objeto de estado mutado desde un reductor Redux. Con Immutable.JS, este problema simplemente no existe, eliminando así toda una clase de errores de tu aplicación.

Esto, junto con su rendimiento y su rica API para la manipulación de datos, es la razón por la que Immutable.JS merece el esfuerzo.

Más información#

Documentación

  • Solución de problemas: No pasa nada cuando despacho una acción

¿Cuáles son algunas de las mejores prácticas opinables para usar Immutable.JS con Redux?#

Immutable.JS puede proporcionar importantes mejoras de fiabilidad y rendimiento a tu app, pero debe usarse correctamente. Si eliges usar Immutable.JS (y recuerda, no estás obligado a hacerlo, y hay otras librerías inmutables que puedes usar), sigue estas buenas prácticas de opinión, y podrás sacarle el máximo partido, sin tropezar con ninguno de los problemas que puede causar potencialmente.

Nunca mezcles objetos JavaScript planos con Immutable.JS#

Nunca dejes que un objeto JavaScript plano contenga propiedades Immutable.JS. Igualmente, nunca dejes que un objeto Immutable.JS contenga un objeto JavaScript plano.

Más información#

Artículos

  • Estructuras de datos inmutables y JavaScript

Haz que todo tu árbol de estado Redux sea un objeto Immutable.JS#

Para una aplicación Redux, todo tu árbol de estado debe ser un objeto Immutable.JS, sin usar objetos JavaScript planos.

  • Crea el árbol usando la función fromJS() de Immutable.JS.

  • Usa una versión Immutable.JS de la función combineReducers, como la de redux-immutable, ya que Redux espera que el árbol de estado sea un objeto JavaScript plano.

  • Cuando se añaden objetos JavaScript a un mapa o lista de Immutable.JS usando Immutable.JS’s update, merge o set métodos, asegúrese de que el objeto que se añade se convierte primero en un objeto Inmutable utilizando fromJS().

Ejemplo

// evitar
const newObj = { key: value }
const newState = state.setIn(, newObj)
// newObj se ha añadido como un objeto JavaScript plano, NO como un mapa Immutable.JS
// recomendado
const newObj = { key: value }
const newState = state.setIn(, fromJS(newObj))
// newObj es ahora un mapa Immutable.JS

Copiar

Más información#

Artículos

  • Estructuras de datos inmutables y JavaScript

Bibliotecas

  • redux-immutable

Usa Immutable.JS en todas partes excepto en tus componentes tontos#

Usar Immutable.JS en todas partes mantiene el rendimiento de tu código. Úsalo en tus componentes inteligentes, en tus selectores, en tus sagas o thunks, en los creadores de acciones y, especialmente, en tus reductores.

No uses, sin embargo, Immutable.JS en tus componentes tontos.

Información adicional#

Artículos

  • Estructuras de datos inmutables y JavaScript
  • Componentes inteligentes y tontos en React

Limita tu uso de toJS()#

toJS()es una función cara y anula el propósito de usar Immutable.JS. Evite su uso.

Más información#

Discusiones

  • Lee Byron en Twitter: «Consejo de perfeccionamiento para #immutablejs…»

Tus selectores deben devolver objetos inmutables.JS#

Siempre. Esta práctica tiene varias ventajas:

  • Evita las repeticiones innecesarias causadas por la llamada a .toJS() en los selectores (ya que .toJS() siempre devolverá un nuevo objeto).
    • Es posible memoizar los selectores donde se llama a .toJS(), pero es redundante cuando basta con devolver objetos Immutable.js sin memoizar.
  • Establece una interfaz consistente para los selectores; no tendrás que estar pendiente de si se devolverá un objeto Immutable.js o un objeto JavaScript plano.

Usa objetos Immutable.JS en tus componentes inteligentes#

Los componentes inteligentes que acceden al almacén a través de la función connect de React Redux deben usar los valores Immutable.JS devueltos por tus selectores. Asegúrese de evitar los problemas potenciales que esto puede causar con el re-renderizado innecesario del componente. Memoize sus selectores utilizando una biblioteca como reselect si es necesario.

Más información#

Documentación

  • Recetas: Cálculo de datos derivados
  • Preguntas frecuentes: Datos inmutables
  • Documentación de Reselect: ¿Cómo uso Reselect con Immutable.js?

Artículos

  • Patrones y antipatrones Redux

Bibliotecas

  • Reselect: Librería de selectores para Redux

Nunca uses toJS() en mapStateToProps#

Convertir un objeto Immutable.JS en un objeto JavaScript usando toJS()devolverá un nuevo objeto cada vez. Si usted hace esto en mapStateToProps, hará que el componente crea que el objeto ha cambiado cada vez que el árbol de estado cambia, y así desencadenar un innecesario re-render.

Más información#

Documentación

  • FAQ: Immutable Data

Nunca uses Immutable.JS en tus componentes tontos#

Tus componentes tontos deben ser puros; es decir, deben producir la misma salida dada la misma entrada, y no tener dependencias externas. Si pasas a un componente de este tipo un objeto Immutable.JS como prop, lo haces dependiente de Immutable.JS para extraer el valor de la prop y manipularlo de otra manera.

Tal dependencia hace que el componente sea impuro, hace que las pruebas del componente sean más difíciles, y hace que la reutilización y refactorización del componente sea innecesariamente difícil.

Más información#

Artículos

  • Estructuras de datos inmutables y JavaScript
  • Componentes inteligentes y tontos en React
  • Consejos para una mejor arquitectura Redux: Lecciones para la escala empresarial

Usa un Componente de Orden Superior para convertir los props Immutable.JS de tu Componente Inteligente en los props JavaScript de tu Componente Mudo#

Algo tiene que mapear los props Immutable.JS de tu Componente Inteligente a los props JavaScript puros usados en tu Componente Mudo. Ese algo es un Componente de Orden Superior (HOC) que simplemente toma los props Immutable.JS de su Componente Inteligente, y los convierte usando toJS() a props de JavaScript puro, que luego son pasados a su Componente Mudo.

Un ejemplo de tal HOC sigue. Un HOC similar está disponible como un paquete NPM para su conveniencia: with-immutable-props-to-js.

import React from ‘react’
import { Iterable } from ‘immutable’
export const toJS = WrappedComponent => wrappedComponentProps => {
const KEY = 0
const VALUE = 1
const propsJS = Object.entries(wrappedComponentProps).reduce(
(newProps, wrappedComponentProp) => {
newProps] = Iterable.isIterable(
wrappedComponentProp
)
? wrappedComponentProp.toJS()
: wrappedComponentProp
return newProps
},
{}
)
return <WrappedComponent {…propsJS} />
}

Copiar

Y así es como lo usarías en tu componente inteligente:

import { connect } from ‘react-redux’
import { toJS } from ‘./to-js’
import DumbComponent from ‘./dumb.component’
const mapStateToProps = state => {
return {
// obj es un objeto Inmutable en Smart Component, pero es convertido a un objeto
// JavaScript plano por toJS, y así se pasa a DumbComponent como un objeto
// JavaScript puro. Debido a que sigue siendo un objeto Immutable.JS aquí en mapStateToProps, sin embargo,
// no hay ningún problema con las re-presentaciones erróneas.
obj: getImmutableObjectFromStateTree(state)
}
}
export default connect(mapStateToProps)(toJS(DumbComponent))

Copiar

Al convertir los objetos Immutable.JS en valores JavaScript planos dentro de un HOC, conseguimos la portabilidad del Dumb Component, pero sin los golpes de rendimiento de usartoJS()en el Smart Component.

Nota: si tu app requiere un alto rendimiento, puede que necesites evitar toJS() por completo, y así tendrás que usar Immutable.JS en tus dumb components. Sin embargo, para la mayoría de las aplicaciones este no será el caso, y los beneficios de mantener Immutable.JS fuera de sus componentes tontos (mantenibilidad, portabilidad y pruebas más fáciles) superarán con creces cualquier mejora de rendimiento percibida por mantenerlo dentro.

Además, el uso de toJS en un Componente de Orden Superior no debería causar mucha, o ninguna, degradación del rendimiento, ya que el componente sólo será llamado cuando los accesorios del componente conectado cambien. Al igual que con cualquier problema de rendimiento, realice primero comprobaciones de rendimiento antes de decidir qué optimizar.

Más información#

Documentación

  • React: Componentes de orden superior

Artículos

  • React Componentes de orden superior en profundidad

Discusiones

  • Redacción: acemarke y cpsubrian comentan sobre Dan Abramov: Redux no es una arquitectura ni un patrón de diseño, es sólo una biblioteca.

Gistas

  • cpsubrian: React decorators for redux/react-router/immutable ‘smart’ components

Usa la extensión de Chrome Immutable Object Formatter para ayudar a la depuración#

Instala el Immutable Object Formatter , e inspecciona tus datos de Immutable.JS sin ver el ruido de las propias propiedades de los objetos de Immutable.JS.

Deja una respuesta

Tu dirección de correo electrónico no será publicada.