Pour modifier des objets / variables profondément imbriqués dans l'état de React, généralement trois méthodes sont utilisées: vanilla JavaScript's Object.assign
, immutability-helper et cloneDeep
from Lodash .
Il existe également de nombreuses autres bibliothèques tierces moins populaires pour y parvenir, mais dans cette réponse, je couvrirai uniquement ces trois options. De plus, il existe des méthodes JavaScript vanille supplémentaires, comme l'étalement de tableaux (voir la réponse de @ mpen par exemple), mais elles ne sont pas très intuitives, faciles à utiliser et capables de gérer toutes les situations de manipulation d'état.
Comme cela a été souligné à maintes reprises dans les commentaires les plus votés des réponses, dont les auteurs proposent une mutation directe de l'État: ne faites pas cela . Il s'agit d'un anti-modèle omniprésent de React, qui entraînera inévitablement des conséquences indésirables. Apprenez de la bonne façon.
Comparons trois méthodes largement utilisées.
Étant donné cette structure d'objet d'état:
state = {
outer: {
inner: 'initial value'
}
}
Vous pouvez utiliser les méthodes suivantes pour mettre à jour la inner
valeur du champ le plus interne sans affecter le reste de l'état.
1. Object.assign de 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>
Gardez à l'esprit que Object.assign n'effectuera pas de clonage en profondeur , car il copie uniquement les valeurs de propriété , et c'est pourquoi ce qu'il fait est appelé une copie superficielle (voir les commentaires).
Pour que cela fonctionne, nous devons seulement manipuler les propriétés de types primitifs ( outer.inner
), c'est-à-dire les chaînes, les nombres, les booléens.
Dans cet exemple, nous créons une nouvelle constante ( const newOuter...
), en utilisant Object.assign
, qui crée un objet vide ( {}
), y copie un outer
objet ( { inner: 'initial value' }
), puis copie un autre objet{ inner: 'updated value' }
dessus .
De cette façon, à la fin, la newOuter
constante nouvellement créée conservera une valeur de { inner: 'updated value' }
puisque leinner
propriété a été remplacée. Il newOuter
s'agit d'un tout nouvel objet, qui n'est pas lié à l'objet en état, il peut donc être modifié selon les besoins et l'état restera le même et ne sera pas modifié jusqu'à ce que la commande de mise à jour soit exécutée.
La dernière partie consiste à utiliser setOuter()
setter pour remplacer l'original outer
en l'état par un nouveaunewOuter
objet (seule la valeur changera, le nom de la propriété outer
ne changera pas).
Imaginez maintenant que nous avons un état plus profond comme state = { outer: { inner: { innerMost: 'initial value' } } }
. Nous pourrions essayer de créer l' newOuter
objet et le remplir avec le outer
contenu de l'état, mais Object.assign
nous ne pourrons pas copierinnerMost
la valeur de cet newOuter
objet nouvellement créé car il innerMost
est imbriqué trop profondément.
Vous pouvez toujours copier inner
, comme dans l'exemple ci-dessus, mais comme il s'agit désormais d'un objet et non d' une primitive, la référence de newOuter.inner
sera copiée dans leouter.inner
, ce qui signifie que nous nous retrouverons avec un newOuter
objet local directement lié à l'objet dans l'état .
Cela signifie que dans ce cas, les mutations de la création locale newOuter.inner
affecteront directement l' outer.inner
objet (en état), car elles sont en fait devenues la même chose (dans la mémoire de l'ordinateur).
Object.assign
par conséquent, cela ne fonctionnera que si vous avez une structure d'état profond à un niveau relativement simple avec des membres les plus internes contenant des valeurs de type primitif.
Si vous avez des objets plus profonds (2e niveau ou plus) que vous devez mettre à jour, ne les utilisez pas Object.assign
. Vous risquez de muter directement l'état.
2. CloneDeep de Lodash
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 de Lodash est beaucoup plus simple à utiliser. Il effectue un clonage en profondeur , c'est donc une option robuste, si vous avez un état assez complexe avec des objets ou des tableaux à plusieurs niveaux à l'intérieur. JustecloneDeep()
la propriété d'état de niveau supérieur, mute la partie clonée de la manière qui vous convient, et setOuter()
retourne à l'état.
3. aide à l'immuabilité
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
il faut à un nouveau niveau, et la chose cool à ce sujet est qu'il peut non seulement des $set
valeurs aux éléments de l' État, mais aussi $push
, $splice
, $merge
(etc.) eux. Voici une liste de commandes disponibles.
Notes annexes
Encore une fois, gardez à l'esprit que cela setOuter
ne modifie que les propriétés de premier niveau de l'objet d'état ( outer
dans ces exemples), pas le profondément imbriqué ( outer.inner
). S'il se comportait différemment, cette question n'existerait pas.
Lequel est bon pour votre projet?
Si vous ne voulez pas ou ne pouvez pas utiliser de dépendances externes et que vous avez une structure d'état simple , respectez-la Object.assign
.
Si vous manipulez un état énorme et / ou complexe , Lodash cloneDeep
est un choix judicieux.
Si vous avez besoin de capacités avancées , c'est-à-dire si votre structure d'état est complexe et que vous devez y effectuer toutes sortes d'opérations, essayez immutability-helper
, c'est un outil très avancé qui peut être utilisé pour la manipulation d'état.
... ou, avez-vous vraiment besoin de faire cela du tout?
Si vous détenez des données complexes dans l'état de React, c'est peut-être le bon moment pour réfléchir à d'autres façons de les gérer. La définition d'un état complexe d'objets directement dans les composants React n'est pas une opération simple, et je suggère fortement de réfléchir à différentes approches.
Il est fort probable que vous feriez mieux de ne pas conserver vos données complexes dans un magasin Redux, de les y placer à l'aide de réducteurs et / ou de sagas et d'y accéder à l'aide de sélecteurs.