Wenn Sie bisher nicht mit Unveränderlichkeit in JavaScript gearbeitet haben, verwechseln Sie diese möglicherweise leicht mit dem Zuweisen einer Variablen zu einem neuen Wert oder der Neuzuweisung. Während es möglich ist, Variablen und Werte, die mit let oder var deklariert wurden, neu zuzuweisen, werden Sie auf Probleme stoßen, wenn Sie dies mit const versuchen.
Nehmen wir an, wir weisen den Wert Kingsley einer Variablen namens firstName zu
let firstName = "Kingsley";
Wir können denselben Variablen einen neuen Wert zuweisen,
firstName = "John";
Dies ist möglich, weil wir let verwendet haben. Wenn wir stattdessen const wie folgt verwenden würden
const lastName = "Silas";
…erhalten wir einen Fehler, wenn wir versuchen, ihn einem neuen Wert zuzuweisen;
lastName = "Doe"
// TypeError: Assignment to constant variable.
Das ist nicht Unveränderlichkeit.
Ein wichtiges Konzept, das Sie bei der Arbeit mit einem Framework wie React hören werden, ist, dass die Veränderung von Zuständen eine schlechte Idee ist. Das Gleiche gilt für Props. Dennoch ist es wichtig zu wissen, dass Unveränderlichkeit kein React-Konzept ist. React nutzt die Idee der Unveränderlichkeit, wenn es mit Dingen wie Zustand und Props arbeitet.
Was zum Teufel bedeutet das? Dort werden wir ansetzen.
Veränderlichkeit bedeutet, bei den Fakten zu bleiben
Unveränderliche Daten können ihre Struktur oder die darin enthaltenen Daten nicht ändern. Es handelt sich um die Festlegung eines Wertes für eine Variable, der sich nicht ändern kann, wodurch dieser Wert zu einer Tatsache oder einer Art Wahrheitsquelle wird – ähnlich wie ein Prinz einen Frosch küsst und hofft, dass er sich in einen schönen Prinzen verwandelt. Unveränderlichkeit besagt, dass dieser Frosch immer ein Frosch bleiben wird.
Objekte und Arrays hingegen erlauben Veränderung, was bedeutet, dass die Datenstruktur geändert werden kann. Das Küssen eines dieser Frösche kann tatsächlich zur Verwandlung eines Prinzen führen, wenn wir es ihm befehlen.
Nehmen wir an, wir haben ein Benutzerobjekt wie dieses
let user = { name: "James Doe", location: "Lagos" }
Als Nächstes versuchen wir, ein newUser-Objekt mit diesen Eigenschaften zu erstellen
let newUser = user
Stellen wir uns nun vor, der erste Benutzer wechselt den Standort. Dies verändert direkt das user-Objekt und beeinflusst auch das newUser
user.location = "Abia"
console.log(newUser.location) // "Abia"
Das ist vielleicht nicht das, was wir wollen. Sie sehen, wie eine solche Neuzuweisung unbeabsichtigte Folgen haben könnte.
Arbeiten mit unveränderlichen Objekten
Wir möchten sicherstellen, dass unser Objekt nicht verändert wird. Wenn wir eine Methode verwenden, muss diese ein neues Objekt zurückgeben. Im Wesentlichen benötigen wir etwas, das als reine Funktion bezeichnet wird.
Eine reine Funktion hat zwei Eigenschaften, die sie einzigartig machen
- Der von ihr zurückgegebene Wert hängt von der übergebenen Eingabe ab. Der zurückgegebene Wert ändert sich nicht, solange sich die Eingaben nicht ändern.
- Sie verändert nichts außerhalb ihres Gültigkeitsbereichs.
Durch die Verwendung von Object.assign() können wir eine Funktion erstellen, die das übergebene Objekt nicht verändert. Diese generiert stattdessen ein neues Objekt, indem sie den zweiten und dritten Parameter in das leere Objekt kopiert, das als erster Parameter übergeben wird. Dann wird das neue Objekt zurückgegeben.
const updateLocation = (data, newLocation) => {
return {
Object.assign({}, data, {
location: newLocation
})
}
}
updateLocation() ist eine reine Funktion. Wenn wir das erste user-Objekt übergeben, gibt sie ein neues user-Objekt mit einem neuen Standortwert zurück.
Ein anderer Weg ist die Verwendung des Spread-Operators
const updateLocation = (data, newLocation) => {
return {
...data,
location: newLocation
}
}
Okay, wie passt das alles in React? Das schauen wir uns als Nächstes an.
Unveränderlichkeit in React
In einer typischen React-Anwendung ist der Zustand ein Objekt. (Redux verwendet ein unveränderliches Objekt als Basis für den Store einer Anwendung.) Der Reconciliation-Prozess von React bestimmt, ob eine Komponente neu gerendert werden soll oder ob eine Möglichkeit benötigt wird, Änderungen zu verfolgen.
Mit anderen Worten: Wenn React nicht herausfinden kann, dass sich der Zustand einer Komponente geändert hat, weiß es nicht, dass das virtuelle DOM aktualisiert werden muss.
Unveränderlichkeit ermöglicht es, Änderungen zu verfolgen, wenn sie erzwungen wird. Dies ermöglicht es React, den alten Zustand mit seinem neuen Zustand zu vergleichen und die Komponente basierend auf diesem Unterschied neu zu rendern.
Aus diesem Grund wird die direkte Aktualisierung des Zustands in React oft abgeraten
this.state.username = "jamesdoe";
React ist sich nicht sicher, ob sich der Zustand geändert hat, und kann die Komponente nicht neu rendern.
Immutable.js
Redux hält sich an die Prinzipien der Unveränderlichkeit. Seine Reducer sind als reine Funktionen gedacht und sollten daher nicht den aktuellen Zustand verändern, sondern ein neues Objekt basierend auf dem aktuellen Zustand und der Aktion zurückgeben. Normalerweise würden wir den Spread-Operator verwenden, wie wir es zuvor getan haben, aber es ist auch möglich, dies mit einer Bibliothek namens Immutable.js zu erreichen.
Während reines JavaScript Unveränderlichkeit handhaben kann, kann es auf dem Weg zu einigen Fallstricken kommen. Die Verwendung von Immutable.js garantiert Unveränderlichkeit und bietet gleichzeitig eine reichhaltige API, die auf Leistung ausgelegt ist. Wir werden in diesem Beitrag nicht auf alle Details von Immutability.js eingehen, aber wir werden ein kurzes Beispiel betrachten, das die Verwendung in einer To-Do-Anwendung zeigt, die von React und Redux angetrieben wird.
Zuerst importieren wir die benötigten Module und richten die Todo-Komponente ein.
const { List, Map } = Immutable;
const { Provider, connect } = ReactRedux;
const { createStore } = Redux;
Wenn Sie lokal mitarbeiten, müssen Sie diese Pakete installiert haben
npm install redux react-redux immutable
Die Importanweisungen sehen wie folgt aus.
import { List, Map } from "immutable";
import { Provider, connect } from "react-redux";
import { createStore } from "redux";
Dann können wir unsere Todo-Komponente mit etwas Markup einrichten
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>
);
};
Wir verwenden die Methode handleSubmit(), um neue To-Do-Elemente zu erstellen. Für dieses Beispiel wird der Benutzer nur neue To-Do-Elemente erstellen und wir benötigen dafür nur eine Aktion
const actions = {
handleNewTodo(text) {
return {
type: "ADD_TODO",
payload: {
id: uuid.v4(),
text
}
};
}
};
Die von uns erstellte payload enthält die ID und den Text des To-Do-Elements. Dann können wir unsere Reducer-Funktion einrichten und die obige Aktion an die Reducer-Funktion übergeben
const reducer = function(state = List(), action) {
switch (action.type) {
case "ADD_TODO":
return state.push(Map(action.payload));
default:
return state;
}
};
Wir werden connect verwenden, um eine Container-Komponente zu erstellen, damit wir uns mit dem Store verbinden können. Dann müssen wir mapStateToProps() und mapDispatchToProps() Funktionen an connect übergeben.
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
);
Wir verwenden mapStateToProps(), um der Komponente die Daten des Stores zuzuführen. Dann verwenden wir mapDispatchToProps(), um die Action Creators als Props für die Komponente verfügbar zu machen, indem wir die Aktion daran binden.
In der Reducer-Funktion verwenden wir List von Immutable.js, um den Anfangszustand der App zu erstellen.
const reducer = function(state = List(), action) {
switch (action.type) {
case "ADD_TODO":
return state.push(Map(action.payload));
default:
return state;
}
};
Stellen Sie sich List wie ein JavaScript-Array vor, weshalb wir die .push()-Methode auf dem Zustand verwenden können. Der Wert, der zur Aktualisierung des Zustands verwendet wird, ist ein Objekt, das angibt, dass Map als Objekt erkannt werden kann. Auf diese Weise ist es nicht notwendig, Object.assign() oder den Spread-Operator zu verwenden, da dies garantiert, dass der aktuelle Zustand nicht geändert werden kann. Dies sieht viel sauberer aus, besonders wenn sich herausstellt, dass der Zustand tief verschachtelt ist – wir brauchen keine Spread-Operatoren überall.
Unveränderliche Zustände ermöglichen es dem Code, schnell festzustellen, ob eine Änderung eingetreten ist. Wir müssen keine rekursive Vergleiche der Daten durchführen, um festzustellen, ob eine Änderung stattgefunden hat. Dennoch ist es wichtig zu erwähnen, dass bei der Arbeit mit großen Datenstrukturen Leistungsprobleme auftreten können – das Kopieren großer Datenobjekte hat seinen Preis.
Aber Daten müssen sich ändern, denn sonst gäbe es keinen Sinn für dynamische Websites oder Anwendungen. Wichtig ist, wie die Daten geändert werden. Unveränderlichkeit bietet die richtige Methode, um die Daten (oder den Zustand) einer Anwendung zu ändern. Dies ermöglicht es, die Änderungen des Zustands zu verfolgen und zu bestimmen, welche Teile der Anwendung als Ergebnis dieser Änderung neu gerendert werden sollen.
Das Erlernen der Unveränderlichkeit wird beim ersten Mal verwirrend sein. Aber Sie werden besser, wenn Sie auf Fehler stoßen, die auftreten, wenn der Zustand verändert wird. Das ist oft der klarste Weg, um die Notwendigkeit und die Vorteile der Unveränderlichkeit zu verstehen.
Hallo,
Nur um darauf hinzuweisen, dass
event.whichveraltet ist und man besserevent.keyCodeverwendet. Der Codepen funktioniert in Chrome 79.0.3945.130 nicht.Der Spread-Operator erstellt keine tiefe Kopie von Objekten.
Kleinigkeit: const ist Unveränderlichkeit, aber nur die Variablenbindung ist unveränderlich (die Abbildung vom Variablennamen zum Variablenwert), nicht der Variablenwert.