Qual é o uso da sintaxe funcional de setState em componentes funcionais react? [duplicado]

Dec 13 2020

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

2 ehab Dec 14 2020 at 01:38

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>
   
    </>
  );
}
1 HeroWanders Dec 13 2020 at 23:59

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 incWithClosurefor 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.

slebetman Dec 13 2020 at 23:26

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.logvalor está obsoleto - o console.logque 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.