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 cloneDeepdo 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 outerobjeto ( { inner: 'initial value' }) e, em seguida, copia um objeto diferente { inner: 'updated value' } sobre ele.
Dessa forma, no final, a newOuterconstante recém-criada manterá um valor { inner: 'updated value' }desde que a innerpropriedade 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 outerno estado por um newOuterobjeto recém-criado (apenas o valor será alterado, o nome da propriedade outernão será).
Agora imagine que temos um estado mais profundo state = { outer: { inner: { innerMost: 'initial value' } } }. Poderíamos tentar criar o newOuterobjeto e preenchê-lo com o outerconteúdo do estado, mas Object.assignnão conseguiremos copiar innerMosto valor desta para o newOuterobjeto 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.innerserá copiada para o outer.innerlugar, o que significa que acabaremos com um newOuterobjeto local diretamente vinculado ao objeto no estado .
Isso significa que, neste caso, mutações do localmente criado newOuter.innerafetarão diretamente o outer.innerobjeto (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-helperleva-lo para um nível totalmente novo, ea coisa legal sobre isso é que ele pode não só $setvaloriza 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 ( outernesses 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.