Nesta postagem, mostrarei como criar um encerramento em um aplicativo React useState hook.
Não vou explicar o que é encerramento, porque existem muitos recursos sobre esse assunto e não quero ser repetitivo. Aconselho a leitura deste artigo de @imranabdulmalik.
Resumindo, um fechamento é (da Mozilla):
...a combinação de uma função agrupada (incluída) com referências ao seu estado circundante (o ambiente lexical). Em outras palavras, um encerramento dá acesso ao escopo de uma função externa a partir de uma função interna. Em JavaScript, os encerramentos são criados toda vez que uma função é criada, no momento da criação da função.
Caso você não esteja familiarizado com o termo ambiente lexical, você pode ler este artigo de @soumyadey ou alternativamente este.
Em um aplicativo React, você pode criar acidentalmente um fechamento de uma variável pertencente ao estado do componente criado com o gancho useState. Quando isso acontece, você está enfrentando um problema de fechamento obsoleto, ou seja, quando você se refere a um valor antigo do estado que entretanto foi alterado e, portanto, não é mais relevante.
Criei uma aplicação Demo React cujo objetivo principal é incrementar um contador (pertencente ao estado) que pode ser fechado em um encerramento no callback do método setTimeout.
Resumindo, este aplicativo pode:
Na imagem a seguir, é mostrado o estado inicial da UI do aplicativo, com contador zero.
Simularemos o fechamento do contador em três passos:
Após 5 segundos, o valor do contador é 2.
O valor esperado do contador deveria ser 12, mas obtemos 2.
A razão pela qual isso acontece é porque criamos um fechamento do contador no callback passado para setTimeout e quando o timeout é acionado definimos o contador a partir de seu valor antigo (era 1).
setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(counter 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000);
Seguindo o código completo do componente do aplicativo.
function App() { const [counter, setCounter] = useState(0) const timeOutInSeconds: number = 5 const [startTimeout, setStartTimeout] = useState (false) const [timeoutInProgress, setTimeoutInProgress] = useState (false) const [logs, setLogs] = useState >([]) useEffect(() => { if (startTimeout && !timeoutInProgress) { setTimeoutInProgress(true) setLogs((l) => [...l, `Timeout scheduled in ${timeOutInSeconds} seconds`]) setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(counter 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000); } }, [counter, startTimeout, timeoutInProgress]) function renderLogs(): React.ReactNode { const listItems = logs.map((log, index) => {log} ); return{listItems}
; } function updateCounter(value: number) { setCounter(value) setLogs([...logs, `The value of counter is now ${value}`]) } function reset() { setCounter(0) setLogs(["reset done!"]) } return (); } export default App;Closure demo
Counter value: {counter}
Follow the istructions to create a closure of the state variable counter
- Set the counter to preferred value
- Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
- Increment by 10 the counter before the timeout
{ renderLogs() }
A solução é baseada no uso do gancho useRef que permite referenciar um valor que não é necessário para renderização.
Então adicionamos ao componente App:
const currentCounter = useRef(counter)
Em seguida, modificaremos o retorno de chamada de setTimeout como mostrado abaixo:
setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${currentCounter.current}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(currentCounter.current 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000);
Nosso retorno de chamada precisa ler o valor do contador porque registramos o valor atual antes de incrementá-lo.
Caso não seja necessário ler o valor, você pode evitar o fechamento do contador apenas usando a notação funcional para atualizar o contador.
seCounter(c => c 1)
Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.
Copyright© 2022 湘ICP备2022001581号-3