Comprender la inmutabilidad en JavaScript

Índice
  1. La mutabilidad se trata de ceñirse a los hechos.
  2. Trabajar con objetos inmutables
  3. Inmutabilidad en reaccionar
  4. Inmutable.js

Si no ha trabajado antes con la inmutabilidad en JavaScript, puede resultarle fácil confundirla con la asignación de una variable a un nuevo valor o la reasignación. Si bien es posible reasignar variables y valores declarados usando let o var, comenzarás a tener problemas cuando lo intentes con const.

Digamos que asignamos valueKingsley a una variable llamada firstName:

let firstName = "Kingsley";

Podemos reasignar un nuevo valor a la misma variable,

firstName = "John";

Esto es posible porque usamos let. Si usamos consten su lugar así:

const lastName = "Silas";

…obtendremos un error cuando intentemos asignarle un nuevo valor;

lastName = "Doe"// TypeError: Assignment to constant variable.

Eso no es inmutabilidad.

Un concepto importante que escuchará al trabajar con un marco, como React, es que los estados mutantes son una mala idea. Lo mismo se aplica a los accesorios. Sin embargo, es importante saber que la inmutabilidad no es un concepto de React. Resulta que React hace uso de la idea de inmutabilidad cuando trabaja con cosas como el estado y los accesorios.

¿Qué diablos significa eso? Ahí es donde vamos a retomar las cosas.

La mutabilidad se trata de ceñirse a los hechos.

Los datos inmutables no pueden cambiar su estructura ni los datos que contienen. Se trata de establecer un valor en una variable que no puede cambiar, haciendo de ese valor un hecho, o algo así como una fuente de verdad, de la misma manera que una princesa besa a una rana con la esperanza de que se convierta en un apuesto príncipe. La inmutabilidad dice que la rana siempre será una rana.

Los objetos y las matrices, por otro lado, permiten la mutación, lo que significa que se puede cambiar la estructura de los datos. Besar a cualquiera de esas ranas puede resultar en la transformación de un príncipe si se lo ordenamos.

Digamos que tenemos un objeto de usuario como este:

let user = { name: "James Doe", location: "Lagos" }

A continuación, intentemos crear un newUserobjeto usando esas propiedades:

let newUser = user

Ahora imaginemos que el primer usuario cambia de ubicación. Mutará directamente el userobjeto y afectará newUsertambién:

user.location = "Abia"console.log(newUser.location) // "Abia"

Puede que esto no sea lo que queremos. Puede ver cómo este tipo de reasignación podría tener consecuencias no deseadas.

Trabajar con objetos inmutables

Queremos asegurarnos de que nuestro objeto no esté mutado. Si vamos a utilizar un método, éste debe devolver un nuevo objeto. En esencia, necesitamos algo llamado función pura .

Una función pura tiene dos propiedades que la hacen única:

  1. El valor que devuelve depende de la entrada pasada. El valor devuelto no cambiará mientras las entradas no cambien.
  2. No cambia cosas fuera de su alcance.

Al usar Object.assign(), podemos crear una función que no mute el objeto que se le pasa. Esto generará un nuevo objeto copiando el segundo y tercer parámetro en el objeto vacío pasado como primer parámetro. Luego se devuelve el nuevo objeto.

const updateLocation = (data, newLocation) = {    return {      Object.assign({}, data, {        location: newLocation    })  }}

updateLocation()es una función pura. Si pasamos el primer userobjeto, devuelve un nuevo userobjeto con un nuevo valor para la ubicación.

Otra forma de hacerlo es utilizar el operador Spread :

const updateLocation = (data, newLocation) = {  return {    ...data,    location: newLocation  }}

Bien, entonces, ¿cómo encaja todo esto en React? Entremos en eso a continuación.

Inmutabilidad en reaccionar

En una aplicación React típica, el estado es un objeto. (Redux utiliza un objeto inmutable como base del almacén de una aplicación). El proceso de reconciliación de React determina si un componente debe volver a renderizarse o si necesita una forma de realizar un seguimiento de los cambios.

En otras palabras, si React no puede darse cuenta de que el estado de un componente ha cambiado, entonces no sabrá que debe actualizar el DOM virtual.

La inmutabilidad, cuando se aplica, hace posible realizar un seguimiento de esos cambios. Esto permite a React comparar el estado anterior de un objeto con su nuevo estado y volver a renderizar el componente en función de esa diferencia.

Es por eso que a menudo se desaconseja actualizar directamente el estado en React:

this.state.username = "jamesdoe";

React no estará seguro de que el estado haya cambiado y no podrá volver a renderizar el componente.

Inmutable.js

Redux se adhiere a los principios de inmutabilidad. Sus reductores están destinados a ser funciones puras y, como tales, no deben mutar el estado actual sino devolver un nuevo objeto basado en el estado y la acción actuales. Normalmente usamos el operador de extensión como lo hicimos antes, pero es posible lograr lo mismo usando una biblioteca llamada Immutable.js .

Si bien JavaScript simple puede manejar la inmutabilidad, es posible encontrarse con algunos obstáculos en el camino. El uso de Immutable.js garantiza la inmutabilidad y al mismo tiempo proporciona una API enriquecida con gran rendimiento. No entraremos en todos los detalles de Immutability.js en este artículo, pero veremos un ejemplo rápido que demuestra su uso en una aplicación de tareas pendientes impulsada por React y Redux.

Primero, comencemos importando los módulos que necesitamos y configuremos el Todocomponente mientras estamos en ello.

const { List, Map } = Immutable;const { Provider, connect } = ReactRedux;const { createStore } = Redux;

Si lo está siguiendo en su máquina local. necesitarás tener estos paquetes instalados:

npm install redux react-redux immutable 

Las declaraciones de importación se verán así.

import { List, Map } from "immutable";import { Provider, connect } from "react-redux";import { createStore } from "redux";

Luego podemos continuar para configurar nuestro Todocomponente con algunas marcas:

const Todo = ({ todos, handleNewTodo }) = {  const handleSubmit = event = {    const text = event.target.value;    if (event.keyCode === 13  text.length  0) {      handleNewTodo(text);      event.target.value = "";    }  };  return (    section className="section"      div className="box field"        label className="label"Todo/label        div className="control"          input            type="text"            className="input"            placeholder="Add todo"            onKeyDown={handleSubmit}          /        /div      /div      ul        {todos.map(item = (          div key={item.get("id")} className="box"            {item.get("text")}          /div        ))}      /ul    /section  );};

Estamos usando el handleSubmit()método para crear nuevas tareas pendientes. A los efectos de este ejemplo, el usuario solo creará nuevas tareas pendientes y solo necesitamos una acción para ello:

const actions = {  handleNewTodo(text) {    return {      type: "ADD_TODO",      payload: {        id: uuid.v4(),        text      }    };  }};

El que payloadestamos creando contiene el ID y el texto de la tarea pendiente. Luego podemos continuar configurando nuestra función reductora y pasar la acción que creamos anteriormente a la función reductora:

const reducer = function(state = List(), action) {  switch (action.type) {    case "ADD_TODO":      return state.push(Map(action.payload));    default:      return state;  }};

Usaremos connectpara crear un componente contenedor para que podamos conectarnos a la tienda. Luego necesitaremos pasar funciones mapStateToProps()y mapDispatchToProps()a connect.

const mapStateToProps = state = {  return {    todos: state  };};const mapDispatchToProps = dispatch = {  return {    handleNewTodo: text = dispatch(actions.handleNewTodo(text))  };};const store = createStore(reducer);const App = connect(  mapStateToProps,  mapDispatchToProps)(Todo);const rootElement = document.getElementById("root");ReactDOM.render(  Provider store={store}    App /  /Provider,  rootElement);

Estamos utilizando mapStateToProps()para suministrar al componente los datos de la tienda. Luego usamos mapDispatchToProps()para que los creadores de acciones estén disponibles como accesorios para el componente al vincularle la acción.

En la función reductora, utilizamos ListImmutable.js para crear el estado inicial de la aplicación.

const reducer = function(state = List(), action) {  switch (action.type) {    case "ADD_TODO":      return state.push(Map(action.payload));    default:      return state;  }};

Piense Listen una matriz de JavaScript, razón por la cual podemos utilizar el .push()método en estado. El valor utilizado para actualizar el estado es un objeto que continúa diciendo que Mappuede reconocerse como un objeto. De esta manera, no es necesario utilizar Object.assign()el operador de propagación, ya que esto garantiza que el estado actual no puede cambiar. Esto parece mucho más limpio, especialmente si resulta que el estado está profundamente anidado: no necesitamos tener operadores dispersos por todas partes.


Los estados inmutables hacen posible que el código determine rápidamente si se ha producido un cambio. No necesitamos hacer una comparación recursiva de los datos para determinar si ocurrió un cambio. Dicho esto, es importante mencionar que es posible que se encuentre con problemas de rendimiento al trabajar con estructuras de datos grandes: la copia de objetos de datos grandes tiene un precio.

Pero los datos deben cambiar porque, de lo contrario, no se necesitan sitios o aplicaciones dinámicos. Lo importante es cómo se cambian los datos. La inmutabilidad proporciona la forma correcta de cambiar los datos (o el estado) de una aplicación. Esto hace posible rastrear los cambios de estado y determinar qué partes de la aplicación deberían volver a representarse como resultado de ese cambio.

Aprender sobre la inmutabilidad la primera vez resultará confuso. Pero mejorarás a medida que te topes con errores que aparecen cuando el estado cambia. Ésta suele ser la forma más clara de comprender la necesidad y los beneficios de la inmutabilidad.

Otras lecturas

  • Inmutabilidad en React y Redux
  • Immutablejs 101 – Mapas y listas
  • Usando Immutable.js con Redux
SUSCRÍBETE A NUESTRO BOLETÍN 
No te pierdas de nuestro contenido ni de ninguna de nuestras guías para que puedas avanzar en los juegos que más te gustan.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

Subir

Este sitio web utiliza cookies para mejorar tu experiencia mientras navegas por él. Este sitio web utiliza cookies para mejorar tu experiencia de usuario. Al continuar navegando, aceptas su uso. Mas informacion