Para modificar objetos / variáveis profundamente aninhados no estado do React, normalmente são utilizados três métodos: JavaScript vanilla Object.assign
, auxiliar da imutabilidade e cloneDeep
do Lodash .
Também existem muitas outras bibliotecas de terceiros menos populares para conseguir isso, mas nesta resposta, abordarei apenas essas três opções. Além disso, existem alguns métodos JavaScript de baunilha adicionais, como a dispersão de array (consulte a resposta do @ mpen, por exemplo), mas eles não são muito intuitivos, fáceis de usar e capazes de lidar com todas as situações de manipulação de estado.
Como foi apontado inúmeras vezes nos principais comentários votados para as respostas, cujos autores propõem uma mutação direta de estado: simplesmente não faça isso . Esse é um antipadrão padrão do React, que inevitavelmente levará a consequências indesejadas. Aprenda da maneira certa.
Vamos comparar três métodos amplamente usados.
Dada essa estrutura de objeto de estado:
state = {
outer: {
inner: 'initial value'
}
}
Você pode usar os seguintes métodos para atualizar o mais interno inner
valor do campo sem afetar o restante do estado.
1. Object.assign do JavaScript Vanilla
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>
Lembre-se de que o Object.assign não executará uma clonagem profunda , pois apenas copia os valores das propriedades , e é por isso que o que faz é chamado de cópia superficial (consulte os comentários).
Para que isso funcione, devemos manipular apenas as propriedades dos tipos primitivos ( outer.inner
), ou seja, cadeias, números, booleanos.
Neste exemplo, estamos criando uma nova constante ( const newOuter...
), using Object.assign
, que cria um objeto vazio ( {}
), copia o outer
objeto ( { inner: 'initial value' }
) e, em seguida, copia um objeto diferente { inner: 'updated value' }
sobre ele.
Dessa forma, no final, a newOuter
constante recém-criada manterá um valor { inner: 'updated value' }
desde que a inner
propriedade foi substituída. Este newOuter
é um objeto totalmente novo, que não está vinculado ao objeto no estado, portanto pode ser alterado conforme necessário e o estado permanecerá o mesmo e não será alterado até que o comando para atualizá-lo seja executado.
A última parte é usar o setOuter()
setter para substituir o original outer
no estado por um newOuter
objeto recém-criado (apenas o valor será alterado, o nome da propriedade outer
não será).
Agora imagine que temos um estado mais profundo state = { outer: { inner: { innerMost: 'initial value' } } }
. Poderíamos tentar criar o newOuter
objeto e preenchê-lo com o outer
conteúdo do estado, mas Object.assign
não conseguiremos copiar innerMost
o valor desta para o newOuter
objeto recém-criado, poisinnerMost
está aninhado muito profundamente.
Você ainda pode copiar inner
, como no exemplo acima, mas como agora é um objeto e não um primitivo, a referência de newOuter.inner
será copiada para o outer.inner
lugar, o que significa que acabaremos com um newOuter
objeto local diretamente vinculado ao objeto no estado .
Isso significa que, neste caso, mutações do localmente criado newOuter.inner
afetarão diretamente o outer.inner
objeto (no estado), uma vez que na verdade se tornaram a mesma coisa (na memória do computador).
Object.assign
portanto, só funcionará se você tiver uma estrutura de estado profundo relativamente simples de um nível, com membros mais internos mantendo valores do tipo primitivo.
Se você tiver objetos mais profundos (2º nível ou mais), que você deve atualizar, não use Object.assign
. Você corre o risco de mudar o estado diretamente.
2. O clone 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>
O cloneDeep da Lodash é muito mais simples de usar. Ele executa uma clonagem profunda , por isso é uma opção robusta, se você tiver um estado bastante complexo com objetos ou matrizes de vários níveis. Apenas cloneDeep()
a propriedade do estado de nível superior, modifique a parte clonada da maneira que desejar e setOuter()
retorne ao estado.
3. auxiliar da imutabilidade
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
leva-lo para um nível totalmente novo, ea coisa legal sobre isso é que ele pode não só $set
valoriza a itens do estado, mas também $push
, $splice
, $merge
(etc.)-los. Aqui está uma lista de comandos disponíveis.
Notas laterais
Novamente, lembre-se de que setOuter
apenas modifica as propriedades de primeiro nível do objeto de estado ( outer
nesses exemplos), não as profundamente aninhadas ( outer.inner
). Se ele se comportasse de maneira diferente, essa pergunta não existiria.
Qual é o certo para o seu projeto?
Se você não quiser ou não puder usar dependências externas e tiver uma estrutura de estado simples , atenha-se a Object.assign
.
Se vocês manipular um estado enorme e / ou complexo , o Lodash cloneDeep
é uma escolha sábia.
Se você precisar recursos avançados , ou seja, se sua estrutura de estado for complexa e você precisar executar todos os tipos de operações, tente immutability-helper
, é uma ferramenta muito avançada que pode ser usada para manipulação de estado.
...ou você realmente precisa fazer isso?
Se você mantiver dados complexos no estado do React, talvez seja um bom momento para pensar em outras maneiras de lidar com eles. A definição correta de objetos de estado complexos nos componentes React não é uma operação simples, e eu sugiro fortemente pensar em abordagens diferentes.
Provavelmente, é melhor você manter seus dados complexos em uma loja Redux, configurando-os lá usando redutores e / ou sagas e acessando-os usando seletores.