Qual é o uso da sintaxe funcional de setState em componentes funcionais react? [duplicado]
estamos falando sobre componentes funcionais com useState
Digamos
const [age, setAge] = useState(0)
agora, digamos que, durante a atualização age
, preciso usar o anteriorage
Os documentos do React mencionam algo chamado ATUALIZAÇÕES FUNCIONAIS, onde você pode passar uma função e o argumento para ela será o valor anterior do estado, por exemplo.
setState((previousAge) => previousAge + 1)
por que eu preciso fazer isso quando posso apenas fazer
setState(previousAge + 1)
quais são os benefícios de usar o funcional setState
,
Eu sei que nos componentes baseados em classe havia algo chamado lote de atualizações de estado no modo funcional, mas não consigo encontrar nada parecido na documentação dos componentes funcionais.
Respostas
Eles não são os mesmos, se sua atualização depende de um valor anterior encontrado no estado, então você deve usar a forma funcional. Se você não usar a forma funcional neste caso, seu código irá quebrar algum dia.
Por que quebra e quando
Os componentes funcionais React são apenas encerramentos, o valor do estado que você tem no encerramento pode estar desatualizado - o que isso significa é que o valor dentro do encerramento não corresponde ao valor que está no estado React para esse componente, isso pode acontecer no seguintes casos:
1- operações assíncronas ( neste exemplo, clique em adição lenta e, em seguida, clique várias vezes no botão adicionar; mais tarde, você verá que o estado foi redefinido para o que estava dentro do fechamento quando o botão adicionar lenta foi clicado)
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
}}
>
immediately add
</button>
<button
onClick={() => {
setTimeout(() => setCounter(counter + 1), 1000);
}}
>
Add
</button>
</>
);
};
2- Quando você chama a função de atualização várias vezes no mesmo fechamento
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick={() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
Add twice
</button>
</>
);
}
Podem ocorrer problemas dependendo da rapidez / freqüência com que seu configurador é chamado.
Se você estiver usando a maneira simples, obtendo o valor do encerramento, as chamadas subsequentes entre dois renderizadores podem não ter o efeito desejado.
Um exemplo simples:
function App() {
const [counter, setCounter] = useState(0);
const incWithClosure = () => {
setCounter(counter + 1);
};
const incWithUpdate = () => {
setCounter(oldCounter => oldCounter + 1);
};
return (<>
<button onClick={_ => { incWithClosure(); incWithClosure(); }}>
Increment twice using incWithClosure
</button>
<button onClick={_ => { incWithUpdate(); incWithUpdate(); }}>
Increment twice using incWithUpdate
</button>
<p>{counter}</p>
</>);
}
Ambos os botões chamam um dos métodos de incremento duas vezes. Mas observamos:
- O primeiro botão aumentará o contador apenas em 1
- O segundo botão aumentará o contador em 2, que provavelmente é o resultado desejado.
Quando isso pode acontecer?
- Obviamente, se
incWithClosure
for chamado várias vezes imediatamente após o outro - Se tarefas assíncronas estiverem envolvidas, isso pode acontecer facilmente (veja abaixo)
- Talvez, se o React tiver muito trabalho a fazer, seus algoritmos de agendamento podem decidir lidar com vários cliques muito rápidos usando o mesmo manipulador de eventos
Exemplo com trabalho assíncrono (simulando o carregamento de um recurso):
function App() {
const [counter, setCounter] = useState(0);
const incWithClosureDelayed = () => {
setTimeout(() => {
setCounter(counter + 1);
}, 1000);
};
const incWithUpdateDelayed = () => {
setTimeout(() => {
setCounter((oldCounter) => oldCounter + 1);
}, 1000);
};
return (
<>
<button onClick={(_) => incWithClosureDelayed()}>
Increment slowly using incWithClosure
</button>
<button onClick={(_) => incWithUpdateDelayed()}>
Increment slowly using incWithUpdate
</button>
<p>{counter}</p>
</>
);
}
Clique no primeiro botão duas vezes (dentro de um segundo) e observe que o contador é incrementado apenas em 1. O segundo botão tem o comportamento correto.
Porque se você não vai encontrar em algum ponto que você obtenha um valor antigo para age
. O problema é que às vezes o que você sugere funciona. Mas às vezes não. Ele pode não quebrar em seu código atual hoje, mas pode quebrar em um código diferente que você escreveu há algumas semanas ou seu código atual alguns meses a partir de agora.
O sintoma é muito, muito estranho. Você pode imprimir o valor da variável dentro de um componente jsx usando a {x}
sintaxe e mais tarde imprimir a mesma variável usando console.log
depois de renderizar o componente jsx (não antes) e descobrir que o console.log
valor está obsoleto - o console.log
que acontece após a renderização pode de alguma forma ter um valor mais antigo do que o render.
Portanto, o valor real das variáveis de estado nem sempre funciona corretamente no código regular - elas são projetadas apenas para retornar o valor mais recente em uma renderização. Por esse motivo, o mecanismo de retorno de chamada em um configurador de estado foi implementado para permitir que você obtenha o valor mais recente de uma variável de estado no código regular fora de uma renderização.