Um tief verschachtelte Objekte / Variablen im Status von React zu ändern, werden normalerweise drei Methoden verwendet: Vanilla JavaScript Object.assign
, Unveränderlichkeitshelfer und cloneDeep
von Lodash .
Es gibt auch viele andere weniger beliebte Bibliotheken von Drittanbietern, um dies zu erreichen, aber in dieser Antwort werde ich nur diese drei Optionen behandeln. Es gibt auch einige zusätzliche Vanilla-JavaScript-Methoden, wie z. B. das Verbreiten von Arrays (siehe z. B. die Antwort von @ mpen), die jedoch nicht sehr intuitiv, einfach zu verwenden und in der Lage sind, alle Zustandsmanipulationssituationen zu bewältigen.
Wie unzählige Male in den oben abgestimmten Kommentaren zu den Antworten erwähnt wurde, deren Autoren eine direkte Mutation des Staates vorschlagen: Tu das einfach nicht . Dies ist ein allgegenwärtiges Reaktionsmuster, das unweigerlich zu unerwünschten Konsequenzen führt. Lerne den richtigen Weg.
Vergleichen wir drei weit verbreitete Methoden.
Angesichts dieser Zustandsobjektstruktur:
state = {
outer: {
inner: 'initial value'
}
}
Sie können die folgenden Methoden verwenden, um das Innerste zu aktualisieren inner
den Wert Felds ohne den Rest des Status zu beeinflussen.
1. Object.assign von Vanilla JavaScript
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the shallow copying:', outer.inner) // initial value
const newOuter = Object.assign({}, outer, { inner: 'updated value' })
console.log('After the shallow copy is taken, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<main id="react"></main>
Beachten Sie, dass Object.assign kein tiefes Klonen ausführt , da nur Eigenschaftswerte kopiert werden . Daher wird das, was es tut, als flaches Kopieren bezeichnet (siehe Kommentare).
Damit dies funktioniert, sollten wir nur die Eigenschaften primitiver Typen ( outer.inner
) bearbeiten, dh Zeichenfolgen, Zahlen, Boolesche Werte.
In diesem Beispiel erstellen wir eine neue Konstante ( const newOuter...
), Object.assign
die ein leeres Objekt ( {}
) erstellt, outer
object ( { inner: 'initial value' }
) hinein kopiert und dann ein anderes Objekt { inner: 'updated value' }
darüber kopiert .
Auf diese Weise enthält die neu erstellte newOuter
Konstante am Ende einen Wert von, { inner: 'updated value' }
da die inner
Eigenschaft überschrieben wurde. Dies newOuter
ist ein brandneues Objekt, das nicht mit dem Objekt im Status verknüpft ist, sodass es nach Bedarf mutiert werden kann. Der Status bleibt unverändert und wird erst geändert, wenn der Befehl zum Aktualisieren ausgeführt wird.
Der letzte Teil besteht darin, den setOuter()
Setter zu verwenden, um das Original outer
im Status durch ein neu erstelltes newOuter
Objekt zu ersetzen (nur der Wert ändert sich, der Eigenschaftsname outer
nicht).
Stellen Sie sich nun vor, wir haben einen tieferen Zustand wie state = { outer: { inner: { innerMost: 'initial value' } } }
. Wir könnten versuchen, das newOuter
Objekt zu erstellen und es mit dem outer
Inhalt des Status Object.assign
zu füllen innerMost
, können jedoch den Wert nicht in dieses neu erstellte newOuter
Objekt kopieren , da innerMost
es zu tief verschachtelt ist.
Sie können weiterhin kopieren inner
, wie im obigen Beispiel, aber da es sich nun um ein Objekt und nicht um ein Grundelement handelt, wird die Referenz von stattdessen auf das newOuter.inner
kopiert outer.inner
, was bedeutet, dass das lokale newOuter
Objekt direkt mit dem Objekt im Status verknüpft ist .
Das bedeutet, dass in diesem Fall Mutationen des lokal erstellten Objekts newOuter.inner
das outer.inner
Objekt (im Status) direkt beeinflussen , da sie tatsächlich dasselbe geworden sind (im Speicher des Computers).
Object.assign
Daher funktioniert es nur, wenn Sie eine relativ einfache einstufige Tiefenzustandsstruktur mit innersten Elementen haben, die Werte vom primitiven Typ enthalten.
Wenn Sie tiefere Objekte (2. Ebene oder mehr) haben, die Sie aktualisieren sollten, verwenden Sie diese nicht Object.assign
. Sie riskieren eine direkte Mutation des Zustands.
2. Lodashs CloneDeep
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
console.log('Before the deep cloning:', outer.inner) // initial value
const newOuter = _.cloneDeep(outer) // cloneDeep() is coming from the Lodash lib
newOuter.inner = 'updated value'
console.log('After the deeply cloned object is modified, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.15/lodash.min.js"></script>
<main id="react"></main>
Lodashs cloneDeep ist viel einfacher zu bedienen. Es führt ein tiefes Klonen durch , daher ist es eine robuste Option, wenn Sie einen ziemlich komplexen Status mit mehrstufigen Objekten oder Arrays haben. Nur cloneDeep()
die State-Eigenschaft der obersten Ebene, mutieren Sie den geklonten Teil nach Belieben und kehren Sie setOuter()
zum State zurück.
3. Unveränderlichkeitshelfer
const App = () => {
const [outer, setOuter] = React.useState({ inner: 'initial value' })
React.useEffect(() => {
const update = immutabilityHelper
console.log('Before the deep cloning and updating:', outer.inner) // initial value
const newOuter = update(outer, { inner: { $set: 'updated value' } })
console.log('After the cloning and updating, the value in the state is still:', outer.inner) // initial value
setOuter(newOuter)
}, [])
console.log('In render:', outer.inner)
return (
<section>Inner property: <i>{outer.inner}</i></section>
)
}
ReactDOM.render(
<App />,
document.getElementById('react')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.10.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.10.2/umd/react-dom.production.min.js"></script>
<script src="https://wzrd.in/standalone/immutability-helper@3.0.0"></script>
<main id="react"></main>
immutability-helper
auf eine ganz neue Ebene dauert es, und die kühle daran ist , dass es nicht nur in $set
dem Zustand Artikel Werte, sondern auch $push
, $splice
, $merge
(etc.) sie. Hier ist eine Liste der verfügbaren Befehle .
Randnotizen
Beachten Sie auch hier, dass setOuter
nur die Eigenschaften der ersten Ebene des Statusobjekts ( outer
in diesen Beispielen) geändert werden, nicht die tief verschachtelten ( outer.inner
). Wenn es sich anders verhalten würde, würde diese Frage nicht existieren.
Welches ist das richtige für Ihr Projekt?
Wenn Sie keine externen Abhängigkeiten verwenden möchten oder können und eine einfache Statusstruktur haben , bleiben Sie bei Object.assign
.
Wenn Sie einen riesigen und / oder komplexen Zustand manipulieren, cloneDeep
ist Lodash eine kluge Wahl.
Wenn Sie erweiterte Funktionen benötigen , dh wenn Ihre Statusstruktur komplex ist und Sie alle Arten von Operationen ausführen müssen, versuchen Sie immutability-helper
, es ist ein sehr erweitertes Tool, das für die Statusmanipulation verwendet werden kann.
... oder müssen Sie das wirklich überhaupt tun?
Wenn Sie komplexe Daten im Status von React halten, ist dies möglicherweise ein guter Zeitpunkt, um über andere Möglichkeiten nachzudenken, wie Sie damit umgehen können. Das Festlegen komplexer Statusobjekte in React-Komponenten ist keine einfache Operation, und ich empfehle dringend, über verschiedene Ansätze nachzudenken.
Höchstwahrscheinlich sollten Sie Ihre komplexen Daten nicht in einem Redux-Speicher aufbewahren, sie dort mithilfe von Reduzierungen und / oder Sagen festlegen und mithilfe von Selektoren darauf zugreifen.