Para modificar objetos / variables profundamente anidados en el estado de React, generalmente se utilizan tres métodos: JavaScript de vainilla Object.assign
, ayudante de inmutabilidad y cloneDeep
de Lodash .
También hay muchas otras librerías de terceros menos populares para lograr esto, pero en esta respuesta, cubriré solo estas tres opciones. Además, existen algunos métodos JavaScript de vainilla adicionales, como la distribución de matrices (consulte la respuesta de @ mpen, por ejemplo), pero no son muy intuitivos, fáciles de usar y capaces de manejar todas las situaciones de manipulación de estado.
Como se señaló innumerables veces en los comentarios más votados a las respuestas, cuyos autores proponen una mutación directa del estado: simplemente no hagas eso . Este es un anti-patrón de Reacción ubicuo, que inevitablemente conducirá a consecuencias no deseadas. Aprende de la manera correcta.
Comparemos tres métodos ampliamente utilizados.
Dada esta estructura de objeto de estado:
state = {
outer: {
inner: 'initial value'
}
}
Puede usar los siguientes métodos para actualizar el inner
valor del campo más interno sin afectar el resto del estado.
1. Vanilla JavaScript Object.assign
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>
Tenga en cuenta que Object.assign no realizará una clonación profunda , ya que solo copia los valores de propiedad , y es por eso que lo que se llama una copia superficial (ver comentarios).
Para que esto funcione, solo debemos manipular las propiedades de los tipos primitivos ( outer.inner
), es decir, cadenas, números, booleanos.
En este ejemplo, estamos creando una nueva constante ( const newOuter...
), usando Object.assign
, que crea un objeto vacío ( {}
), copia el outer
objeto ( { inner: 'initial value' }
) en él y luego copia un objeto diferente { inner: 'updated value' }
sobre él.
De esta manera, al final, la newOuter
constante recién creada mantendrá un valor { inner: 'updated value' }
desde que la inner
propiedad se anuló. Este newOuter
es un objeto nuevo, que no está vinculado al objeto en estado, por lo que puede mutarse según sea necesario y el estado permanecerá igual y no cambiará hasta que se ejecute el comando para actualizarlo.
La última parte es usar setOuter()
setter para reemplazar el original outer
en el estado con un newOuter
objeto recién creado (solo cambiará el valor, no cambiará el nombre de la propiedad outer
).
Ahora imagine que tenemos un estado más profundo como state = { outer: { inner: { innerMost: 'initial value' } } }
. Podríamos intentar crear el newOuter
objeto y llenarlo con los outer
contenidos del estado, pero Object.assign
no podremos copiar innerMost
el valor de este newOuter
objeto recién creado ya que innerMost
está anidado demasiado profundamente.
Todavía podría copiar inner
, como en el ejemplo anterior, pero como ahora es un objeto y no un primitivo, la referencia de newOuter.inner
se copiará en su outer.inner
lugar, lo que significa que terminaremos con un newOuter
objeto local directamente vinculado al objeto en el estado .
Eso significa que en este caso las mutaciones de lo creado localmente newOuter.inner
afectarán directamente al outer.inner
objeto (en estado), ya que de hecho se convirtieron en lo mismo (en la memoria de la computadora).
Object.assign
por lo tanto, solo funcionará si tiene una estructura de estado profundo de un nivel relativamente simple con miembros más internos que tengan valores del tipo primitivo.
Si tiene objetos más profundos (segundo nivel o más), que debe actualizar, no los use Object.assign
. Corre el riesgo de mutar directamente.
2. El clon de Lodash Profundo
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>
El clonDeep de Lodash es mucho más simple de usar. Realiza una clonación profunda , por lo que es una opción sólida, si tiene un estado bastante complejo con objetos o matrices de varios niveles dentro. Solo cloneDeep()
la propiedad de estado de nivel superior, mute la parte clonada de la forma que desee y setOuter()
regrese al estado.
3. ayudante de inmutabilidad
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 lleva a un nivel completamente nuevo, y el fresco de todo es que no sólo se $set
valora a los elementos estatales, sino también $push
, $splice
, $merge
(etc.) ellos. Aquí hay una lista de comandos disponibles.
Notas al margen
Nuevamente, tenga en cuenta que eso setOuter
solo modifica las propiedades de primer nivel del objeto de estado ( outer
en estos ejemplos), no las profundamente anidadas ( outer.inner
). Si se comportara de manera diferente, esta pregunta no existiría.
¿Cuál es el adecuado para su proyecto?
Si no quiere o no puede usar dependencias externas , y tiene una estructura de estado simple , siga Object.assign
.
Si manipulas un estado enorme y / o complejo , Lodash's cloneDeep
es una buena elección.
Si necesita capacidades avanzadas , es decir, si su estructura de estado es compleja y necesita realizar todo tipo de operaciones, intente immutability-helper
, es una herramienta muy avanzada que puede usarse para la manipulación de estados.
... o, ¿ realmente necesitas hacer esto?
Si tiene datos complejos en el estado de React, quizás este sea un buen momento para pensar en otras formas de manejarlos. Establecer un objeto de estado complejo en los componentes React no es una operación sencilla, y sugiero encarecidamente que piense en diferentes enfoques.
Lo más probable es que sea mejor que guarde sus datos complejos en una tienda Redux, configurándolos allí usando reductores y / o sagas y accediendo a ellos usando selectores.