DEV Community

Cover image for Inmutabilidad
leobar37
leobar37

Posted on

Inmutabilidad

El concepto de inmutabilidad es muy importante dentro del entorno de React, ya que sin dominar
este concepto, podemos estar causando renderizados extra o efectos secundarios dentro de nuestra aplicación.

¿Qué es inmutabilidad?

Un objeto inmutable es aquel objeto que no puede ser modificado una vez creado, no puede cambiar su estructura ni los datos que contiene, este no es el caso de las constantes, porque cuando sea crea una constante, quizás no se puede reasignar el valor, pero si cambiar su estructura.

const case1 = {
  nem: "nem",
  nim: "nim",
};
case1.nim = "nom";
/*
{
   "nem": "nem",
   "nim": "nom"
}
*/
const case2 = {
  nem: "Hello",
};
// Cannot assign to 'case2' because it is a constant
case2 = case1;
Enter fullscreen mode Exit fullscreen mode

Este ejemplo demuestra que las constantes puede ser modificadas, pero no pueden reasignarse.

Para entender que es inmutabilidad un poco más clara hay que echar un vistazo a como funcionan los objetos en JavaScript.

Valores inmutables

Los valores inmutables o primitivos son aquellos que se almacenan completos en memoria, estos son strings, numbers, booleans, null y undefined. Es decir, estos tiene un lugar propio en memoria.

let case1 = "wenas";
let case2 = "wenas";
Enter fullscreen mode Exit fullscreen mode

Por ejemplo estos dos variables son almacenadas de la siguiente manera:

memory 1

Entonces al ser objetos primitivos, su valor es el mismo

const areEqual = (a, b) => {
  return a === b;
};

const case1 = "wenas";
const case2 = "wenas";

console.log(areEqual(case1, case2));
//  true
Enter fullscreen mode Exit fullscreen mode

Valores mutables:

En el caso de los objetos no pasa lo mismo que con los primitivos, estos son almacenados de manera distinta.

const case3 = { name: "pepe", lastName: "perez" };
const case4 = { name: "pepe", lastName: "perez" };

console.log(areEqual(case3, case4));
// false
Enter fullscreen mode Exit fullscreen mode

Eso es porque una variable asignada a un objeto, no es almacenado en el objeto en sí, este solo almacena su dirección de memoria.

object reference

Se puede decir que un objeto es como una agenda con las direcciones
de memoria de otros objetos, entonces cuando hace algo con alguna de las propiedades como case.name el motor de JavaScript toma esa dirección de memoria y realiza la operación en el objeto real.

Copiar un objeto:

Cuando copiamos un objeto, copiamos la referencia, Por ejemplo:
Supongamos que tenemos un objeto usuario y queremos copiarlo, para a partir de él, crear un usuario 2.

const user1 = { name: "pepe", lastName: "perez" };
const user2 = user1;
console.log(areEqual(user1, user2));
// true
Enter fullscreen mode Exit fullscreen mode

En este caso podríamos decir que user2 tiene la misma información a que user1 y, por lo tanto, son iguales.

const user1 = { name: "pepe", lastName: "perez" };
const user2 = user1;
user2.name = "pepe2";
console.log(areEqual(user1, user2));
// true
Enter fullscreen mode Exit fullscreen mode

Pero si alteramos la propiedad de user2, siguen siendo iguales, ¿Por qué?, bueno, eso es porque al hacer const user2=user1 estamos creando una referencia a las mismas direcciones de memoria, y al afectar user2.name afecta a user1.name también.

const user1 = { name: "pepe", lastName: "perez" };
const user2 = user1;
user2.name = "pepe2";

console.log(areEqual(user1, user2));
// true
console.log({
  user1,
  user2,
});

/**
 *  {
 *   user1 : {
 *    name :"pepe2",
 *    lastName :"perez"
 *  },
 *   user2 : {
 *    name :"pepe2",
 *    lastName :"perez"
 *  }
 * }
 */
Enter fullscreen mode Exit fullscreen mode

Esto se tiene que tener en cuenta a la hora de escribir código.
Porque podemos crear efectos secundarios sin querer. La manera efectiva de hacerlo sería la siguiente.

const user1 = { name: "pepe", lastName: "perez" };
const user2 = { ...user1 };
user2.name = "pepe2";
console.log(areEqual(user1, user2));
// false
Enter fullscreen mode Exit fullscreen mode

En este caso, al utilizar spreed operator, para sacar toda la propiedad de user1 y pasársela a user2 el cual es un nuevo objeto(nueva direcciones de memoria). También podemos utilizar object.assign

Inmutabilidad en React

Provocar efectos secundarios en React puede ser peligroso. EnReact para hacer la diferencia del estado anterior y el nuevo devuelto, hace igualdad estricta, porque, sería un impacto de performance hacer una igualación a profundidad.

Por eso es que se prohíbe el uso de cualquier método que mute el estado, como en el caso de los array como Array.prototype.push porque mutan el array, a diferencia de [Array.prototype.filter] el cual devuelve uno nuevo.

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.
docs

Por ejemplo, hacer esto es incorrecto.

export default function App() {
  const [array, setArray] = React.useState([1, 2]);
  return (
    <button
      onClick={() => {
        array.push(3);
        setArray(array);
      }}
    ></button>
  );
}
Enter fullscreen mode Exit fullscreen mode

la forma correcta de hacerlo sería la siguiente:

export default function App() {
  const [array, setArray] = React.useState([1, 2]);
  return (
    <button
      onClick={() => {
        setArray([...array, 4]);
      }}
    ></button>
  );
}
Enter fullscreen mode Exit fullscreen mode

Immer al rescate

Teniendo en cuenta la inmutabilidad y su importancia dentro de nuestras aplicaciones, en cierto punto puedes toparte con código así.

const state = {
  prop1: {
    prop11: "1",
    prop12: "2",
    prop13: {
      prop131: "1",
      prop132: {
        prop1321: "1",
      },
    },
  },
  prop2: {
    prop21: "2",
  },
};

// changue  prop1321 to "2"  :)

const nextState = {
  ...state,
  prop1: {
    ...state.prop1,
    prop13: {
      ...state.prop1.prop13,
      prop132: {
        ...state.prop1.prop13.prop132,
        prop1321: "2", // :(
      },
    },
  },
};
Enter fullscreen mode Exit fullscreen mode

Immer que hace que la inmutabilidad sea facil de hacer.

import produce from "immer";

const state = {
  prop1: {
    prop11: "1",
    prop12: "2",
    prop13: {
      prop131: "1",
      prop132: {
        prop1321: "1",
      },
    },
  },
  prop2: {
    prop21: "2",
  },
};

const nextState = produce(state, (draftState) => {
  draftState.prop1.prop13.prop132.prop1321 = "2"; //:)
});
Enter fullscreen mode Exit fullscreen mode

Lo que hace immer es aplicar temporalmente los cambios a drafStateel cual es un proxy. Una vez que la mutación es completada, immer se encarga de producir el siguiente estado, basado en las mutaciones a draftState.

De esta forma no tenemos que preocuparnos de afectar el estado, de hecho, ni de retornar el estado de borrador. Immer ofrce otras características, como utilities para facilitar su uso e integración con react con el paquete use-immer.

Conclusiones

  • Declarar una variable como constante, evita que se reasigne más no que sea mutada.
  • Entender inmutabilidad puede ser fundamental en React, porque eso evita que se produzcan efectos secundarios.
  • Immer facilita la inmutabilidad encargándose de todo el proceso y encargándose de que el resultado sea el correcto.

Happy coding :)

Top comments (0)