"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > React: fechamento obsoleto

React: fechamento obsoleto

Publicado em 2024-09-02
Navegar:663

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.

O problema

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.

POC

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:

  • Mostrar o valor do contador
  • Incrementar em 1 o contador
  • Inicie um cronômetro para aumentar o contador em 1 após cinco segundos.
  • Incrementar em 10 o contador

Na imagem a seguir, é mostrado o estado inicial da UI do aplicativo, com contador zero.

React: stale closure

Simularemos o fechamento do contador em três passos:

  1. Incrementando em 1 o contador

React: stale closure

  1. Iniciando o cronômetro para aumentar 1 após cinco segundos

React: stale closure

  • Incrementando em 10 antes que o tempo limite seja acionado React: stale closure

Após 5 segundos, o valor do contador é 2.

React: stale closure

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 (

    Closure demo


    Counter value: {counter}


    Follow the istructions to create a closure of the state variable counter

    1. Set the counter to preferred value
    2. Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
    3. Increment by 10 the counter before the timeout

    { renderLogs() }
    ); } export default App;

    Solução

    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)
    

    Recursos

    • Dmitri Pavlutin Esteja ciente de fechamentos obsoletos ao usar ganchos React
    • Imran Abdulmalik Dominando Closures em JavaScript: Um Guia Abrangente
    • Keyur Paralkar Lexical Scope em JavaScript – Guia para iniciantes
    • Fechamentos obsoletos de Souvik Paul em reação
    • Soumya Dey Compreendendo o escopo lexical e os fechamentos em JavaScript
    • Stackoverflow de Subash Mahapatra
    Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/animusna/react-stale-closure-81a?1 Se houver alguma violação, entre em contato com [email protected] para excluí-lo
    Tutorial mais recente Mais>

    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