Per modificare oggetti / variabili profondamente annidati nello stato di React, in genere vengono utilizzati tre metodi: JavaScript vanilla Object.assign, immutability-helper e cloneDeepda 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' outeroggetto ( { inner: 'initial value' }) in esso e quindi copia un oggetto diverso { inner: 'updated value' } su di esso.
In questo modo, alla fine, la newOutercostante appena creata conterrà un valore { inner: 'updated value' }poiché la innerproprietà è 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 outernello stato con un newOuteroggetto 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' newOuteroggetto e popolarlo con ilouter contenuti dello stato, ma Object.assignnon saremo in grado di copiare innerMostil valore su questo newOuteroggetto 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.innerverrà copiato nell'oggetto outer.innerinvece, il che significa che finiremo con l' newOuteroggetto locale direttamente legato all'oggetto nello stato .
Ciò significa che in questo caso le mutazioni del creato localmente newOuter.innerinfluenzeranno direttamente l' outer.inneroggetto (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-helperlo prende ad un livello completamente nuovo, e il bello di tutto ciò è che può non solo $seti 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ò setOutermodifica solo le proprietà di primo livello dell'oggetto stato ( outerin 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.