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;
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";
Por ejemplo estos dos variables son almacenadas de la siguiente manera:
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
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
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.
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
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
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"
* }
* }
*/
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
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>
);
}
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>
);
}
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", // :(
},
},
},
};
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"; //:)
});
Lo que hace immer es aplicar temporalmente los cambios a drafState
el 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)