Per modificare oggetti / variabili profondamente annidati nello stato di React, in genere vengono utilizzati tre metodi: JavaScript vanilla Object.assign
, immutability-helper e cloneDeep
da Lodash .
Ci sono anche molte altre librerie di terze parti meno popolari per raggiungere questo obiettivo, ma in questa risposta tratterò solo queste tre opzioni. Inoltre, esistono altri metodi JavaScript di vaniglia, come la diffusione di array, (vedi la risposta di @ mpen per esempio), ma non sono molto intuitivi, facili da usare e in grado di gestire tutte le situazioni di manipolazione dello stato.
Come è stato sottolineato innumerevoli volte nei commenti più votati alle risposte, i cui autori propongono una mutazione diretta dello stato: proprio non farlo . Questo è un onnipresente anti-pattern di React, che porterà inevitabilmente a conseguenze indesiderate. Impara nel modo giusto.
Confrontiamo tre metodi ampiamente utilizzati.
Data questa struttura di oggetti stato:
state = {
outer: {
inner: 'initial value'
}
}
È possibile utilizzare i seguenti metodi per aggiornare quello più interno inner
valore del campo senza influire sul resto dello stato.
1. Object.assign di 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>
Tieni presente che Object.assign non eseguirà una clonazione approfondita , poiché copia solo i valori delle proprietà ed è per questo che ciò che fa viene chiamato copia superficiale (vedi commenti).
Perché questo funzioni, dovremmo solo manipolare le proprietà dei tipi primitivi ( outer.inner
), ovvero stringhe, numeri, valori booleani.
In questo esempio, stiamo creando una nuova costante (const newOuter...
), utilizzando Object.assign
, che crea un oggetto vuoto ( {}
), copia l' outer
oggetto ( { inner: 'initial value' }
) in esso e quindi copia un oggetto diverso { inner: 'updated value' }
su di esso.
In questo modo, alla fine, la newOuter
costante appena creata conterrà un valore { inner: 'updated value' }
poiché la inner
proprietà è stata sostituita. Questo newOuter
è un oggetto nuovo di zecca, che non è collegato all'oggetto in stato, quindi può essere mutato in base alle esigenze e lo stato rimarrà lo stesso e non verrà modificato fino a quando non verrà eseguito il comando per aggiornarlo.
L'ultima parte consiste nell'utilizzare setOuter()
setter per sostituire l'originale outer
nello stato con un newOuter
oggetto appena creato (cambia solo il valore, il nome della proprietàouter
no).
Ora immagina di avere uno stato più profondo come state = { outer: { inner: { innerMost: 'initial value' } } }
. Potremmo provare a creare l' newOuter
oggetto e popolarlo con ilouter
contenuti dello stato, ma Object.assign
non saremo in grado di copiare innerMost
il valore su questo newOuter
oggetto appena creato poichéinnerMost
è nidificato troppo in profondità.
Potresti comunque copiare inner
, come nell'esempio sopra, ma dato che ora è un oggetto e non una primitiva, il riferimento da newOuter.inner
verrà copiato nell'oggetto outer.inner
invece, il che significa che finiremo con l' newOuter
oggetto locale direttamente legato all'oggetto nello stato .
Ciò significa che in questo caso le mutazioni del creato localmente newOuter.inner
influenzeranno direttamente l' outer.inner
oggetto (nello stato), poiché in realtà sono diventate la stessa cosa (nella memoria del computer).
Object.assign
pertanto funzionerà solo se si dispone di una struttura di stato profondo a un livello relativamente semplice con membri più interni che contengono valori di tipo primitivo.
Se hai oggetti più profondi (2 ° livello o più), che dovresti aggiornare, non usare Object.assign
. Rischi direttamente lo stato di mutazione.
2. Clone di Lodash Profondo
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>
CloneDeep di Lodash è molto più semplice da usare. Esegue una clonazione profonda , quindi è un'opzione solida, se si dispone di uno stato abbastanza complesso con oggetti o matrici multilivello all'interno. Solo cloneDeep()
la proprietà dello stato di livello superiore, muta la parte clonata nel modo che preferisci, esetOuter()
torna allo stato.
3. aiutante dell'immutabilità
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
lo prende ad un livello completamente nuovo, e il bello di tutto ciò è che può non solo $set
i valori agli elementi di stato, ma anche $push
, $splice
, $merge
(ecc) di loro. Ecco un elenco di comandi disponibili.
Note a margine
Ancora una volta, tieni presente che ciò setOuter
modifica solo le proprietà di primo livello dell'oggetto stato ( outer
in questi esempi), non i nidificati in profondità (outer.inner
). Se si comportasse in modo diverso, questa domanda non esisterebbe.
Quale è giusto per il tuo progetto?
Se non si desidera o non si possono utilizzare dipendenze esterne e si dispone di una struttura di stato semplice , attenersi a Object.assign
.
Se manipoli uno stato enorme e / o complesso , quella di Lodash cloneDeep
è una scelta saggia.
Se hai bisogno di funzionalità avanzate , ad esempio se la tua struttura statale è complessa e devi eseguire tutti i tipi di operazioni su di essa, prova immutability-helper
, è uno strumento molto avanzato che può essere utilizzato per la manipolazione dello stato.
... o hai davvero bisogno di farlo?
Se conservi dati complessi nello stato di React, forse è un buon momento per pensare ad altri modi di gestirli. Impostare oggetti di stato complessi proprio nei componenti di React non è un'operazione semplice e suggerisco vivamente di pensare a diversi approcci.
Molto probabilmente faresti meglio a conservare i tuoi dati complessi in un negozio Redux, impostarli lì utilizzando riduttori e / o saghe e accedervi utilizzando i selettori.