Con l’avvento di Redux e altri framework JavaScript simili che cercano di essere un più funzionali (funzionali come nella programmazione funzionale), un nuovo problema è stato introdotto nei programmatori Javascript (almeno per quelli non familiari con la programmazione funzionale): come mantenere lo stato della loro applicazione “immutabile“.
Ma prima di tutto: Cosa si intende per immutabilità?
Immutabile significa che lo stato dovrebbe essere un oggetto che non cambia (mutabile) – invece di cambiarlo, Redux ha bisogno che l’oggetto stato venga creato dall’inizio (se siete curiosi ecco come viene gestito da Redux.
Ovviamente non parleremo di come Redux opera con l’immutabilità (ci sarà un mini corso ad hoc in futuro – lo prometto 🙂 )ma bensì il nostro goal sarà come ES6 tratta oggetti immutabili.
Array
Il metodo push(), nonostante sia conveniente in molti casi, non può essere utilizzato quando vogliamo trattare l’immutabilità. Ecco un esempio di come il metodo push() influisce sulle variabili passate in una funzione.
function addNew(arr) {
return arr.push(2);
}
var original = [];
var newArray = addNew(original);
console.log(original); // [2]
console.log(newArray); // [2]
Quando si passano i parametri in una funzione, normalmente ci si aspetta che la funzione crei una copia dei parametri da utilizzare all’interno del corpo della funzione. Tuttavia, poiché gli array sono variabili di riferimento, il riferimento che passiamo in una funzione sarà lo stesso riferimento utilizzato all’interno del corpo della funzione. Pertanto, quando eseguiamo operazioni come push() su quel riferimento, influisce anche sull’array originale al di fuori della funzione (argomento trattato già qui).
Potremmo aggirare questo “problema” usando l’operatore concat() invece di push(). concat() restituisce effettivamente un nuovo array quando viene chiamato, costituito dai due array che vengono passati come parametri. L’esempio mostrato prima diventa:
function addNew(arr) {
return arr.concat(2);
}
var original = [];
var newArray = addNew(original);
console.log(original); // []
console.log(newArray); // [2]
In questo caso abbiamo che la funzione addNew, crea un nuovo array e non influisce sull’array originale.
Tecnicamente questo è il risultato che volevamo ma, introducendo ES6, possiamo rendere il codice più leggibile utilizzando la spread syntax:
// ES2015
function addNew(arr) {
return [...arr, 2];
}
var original = [];
var newArray = addNew(original);
console.log(original); // []
console.log(newArray); // [2]
In modo simile, possiamo effettivamente rimuovere un elemento da un array usando l’operatore di spread, mentre continuiamo a mantenere l’immutabilità. In questo esempio, slice() viene utilizzato come metodo sicuro che restituisce una copia “shallow” di un array.
function remove(arr, index) {
return [...arr.slice(0, index), ...arr.slice(index + 1)];
}
let original = ["1", "2", "3", "4"];
let newArray = remove(original, 2);
console.log(original); // ["1", "2", "3", "4"]
console.log(newArray); // ["1", "2", "4"]
Objects
Poiché gli oggetti sono anche variabili di riferimento, non possiamo passarli in una funzione e modificarli direttamente senza mutare la variabile originale. Come per gli array, quando si usano gli oggetti, dobbiamo trovare un modo per creare una funzione che modifichi l’oggetto restituendo un oggetto nuovo di zecca.
Fortunatamente, ci viene in soccorso ES6, con l’operatore Object.assign().
Diciamo che stiamo sviluppando la classica applicazione TODO list, prendiamo in esame un oggetto come questo:
objTODO = {
id: 0,
text: "Sto imparando ad essere immutabile",
completed: false
};
Vogliamo creare una funzione che abiliti o meno il completamento di un todo.
Potremmo semplicemente fare un assegnazione all’oggetto e settare completed a true ma, come abbiamo detto precedemente, questa è una mutazione che vogliamo evitare.
La soluzione ci viene data da ES6, ovvero l’utilizzo dell’operatore Object.assign(). Prendiamo in esame questo snippet di codice:
function toggleTodo(todo) {
// Copiamo le proprietà di todo in un nuovo oggetto
// e facciamo sovrascriviamo la proprietà completa con
// il nuovo valore
return Object.assign({}, todo, {
completed: !todo.completed
});
}
let newTodo = toggleTodo(exampleTodo);
console.log(exampleTodo);
// { id: 0,
// text: 'Sto imparando ad essere immutabile',
// completed: false }
console.log(newTodo);
// { id: 0,
// text: 'Sto imparando ad essere immutabile',
// completed: true}
Object.assign() funziona copiando i valori delle proprietà dei suoi parametri in un nuovo oggetto. Il primo parametro del metodo Object.assign() definisce l’oggetto target che vogliamo creare, che nel nostro caso è un oggetto vuoto, {}. Tutti gli altri parametri definiscono le proprietà che vogliamo copiare in questo nuovo oggetto.
Ora possiamo modificare con successo le informazioni contenute nei nostri todos senza mutare il todo originale.
Conclusioni
Utilizzando le tecniche elencate sopra si dovrebbe essere in grado di mantenere facilmente gli oggetti di stato immutabili. Indicativamente per casi più complessi sarebbe opportuno adoperare soluzioni più avanzate come poor-man-lens npm package.
Spero questo articolo sia stato utile. Se si, condividilo con i tuoi amici, colleghi o con chi ritieni che possa essere interessato.
PS. Se sei interessato a conoscere TUTTI gli argomenti della miniserie, ti consiglio di visitare il primo articolo.
Con questo articolo si chiude la mini serie dedicata ad ES6.
Ci aspettano nuovi argomenti da affrontare ovviamente 🙂
Stay tuned
Valerio Pisapia, 34 anni, laureato in Ingegneria Informatica all’Università di Napoli Federico II. Ha esperienza decennale nello sviluppo mobile e web in ambito internazionale. Fornisce training e mentoring per progetti di svariata natura. Founder della Dreaming Lab, startup IT in Svizzera, che si occupa di consulenza specializzata in ambito Web.
Comments