O React mantém o pedido de atualizações de estado?

139

Eu sei que o React pode executar atualizações de estado de forma assíncrona e em lote para otimização do desempenho. Portanto, você nunca pode confiar no estado a ser atualizado depois de ligar setState. Mas você pode confiar Reagir para atualizar o estado na mesma ordem como setStateé chamado para

  1. o mesmo componente?
  2. componentes diferentes?

Considere clicar no botão nos seguintes exemplos:

1. Existe alguma possibilidade de que a seja falso eb seja verdadeiro para:

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false, b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.setState({ a: true });
    this.setState({ b: true });
  }
}

2. Existe a possibilidade de que a seja falso eb seja verdadeiro para:

class SuperContainer extends React.Component {
  constructor(props) {
    super(props);
    this.state = { a: false };
  }

  render() {
    return <Container setParentState={this.setState.bind(this)}/>
  }
}

class Container extends React.Component {
  constructor(props) {
    super(props);
    this.state = { b: false };
  }

  render() {
    return <Button onClick={this.handleClick}/>
  }

  handleClick = () => {
    this.props.setParentState({ a: true });
    this.setState({ b: true });
  }
}

Lembre-se de que essas são simplificações extremas do meu caso de uso. Percebo que posso fazer isso de maneira diferente, por exemplo, atualizando os dois parâmetros de estado ao mesmo tempo no exemplo 1, bem como executando a segunda atualização de estado em um retorno de chamada para a primeira atualização de estado no exemplo 2. No entanto, essa não é minha pergunta, e só estou interessado se houver uma maneira bem definida de o React executar essas atualizações de estado, nada mais.

Qualquer resposta apoiada pela documentação é muito apreciada.

darksmurf
fonte
3
não parece uma pergunta sem sentido, você também pode fazer essa pergunta nos problemas do github da página de reação; dan abramov geralmente é bastante útil lá. Quando eu tinha perguntas tão complicadas, eu perguntava e ele respondia. O ruim é que esse tipo de problema não é compartilhado publicamente como nos documentos oficiais (para que outras pessoas também possam acessá-lo facilmente). Eu também sinto Reagir documentação oficial carece de uma extensa cobertura de alguns temas como o tema da sua pergunta, etc.
giorgim
Por exemplo tomar este: github.com/facebook/react/issues/11793 , eu acredito que o material discutido nessa edição seria útil para muitos desenvolvedores, mas esse material não é sobre os documentos oficiais, porque as pessoas FB considerar que avançado. O mesmo é sobre outras coisas, possivelmente. Eu pensaria que um artigo oficial intitulado algo como 'a administração do estado reaja em profundidade' ou 'armadilhas da administração do estado' que explora todos os casos de canto da administração do estado, como na sua pergunta, não seria ruim. talvez nós podemos empurrar os desenvolvedores FB para estender a documentação com tais coisas :)
giorgim
Há um link para um ótimo artigo sobre mídia na minha pergunta. Deve cobrir 95% dos casos de uso do estado. :)
Michal
2
@Michal, mas esse artigo ainda não responde a essa pergunta IMHO
giorgim

Respostas:

335

Eu trabalho no React.

TLDR:

Mas você pode confiar em React para atualizar o estado na mesma ordem em que setState é chamado para

  • o mesmo componente?

Sim.

  • componentes diferentes?

Sim.

A ordem das atualizações é sempre respeitada. Se você vê um estado intermediário "entre" eles ou não, depende se você está dentro de um lote ou não.

Atualmente (React 16 e versões anteriores), somente as atualizações dentro dos manipuladores de eventos React são agrupadas por padrão . Existe uma API instável para forçar lotes fora dos manipuladores de eventos para casos raros quando você precisar.

Nas versões futuras (provavelmente React 17 e posterior), o React irá agrupar todas as atualizações por padrão, para que você não precise pensar nisso. Como sempre, anunciaremos quaisquer alterações sobre isso no blog do React e nas notas de versão.


A chave para entender isso é que, não importa quantas setState()chamadas sejam feitas em quantos componentes você faz dentro de um manipulador de eventos React , eles produzirão apenas uma única re-renderização no final do evento . Isso é crucial para um bom desempenho em aplicativos grandes, pois se Childe Parentcada chamada setState()ao manipular um evento de clique, você não deseja renderizar novamente Childduas vezes.

Nos dois exemplos, as setState()chamadas acontecem dentro de um manipulador de eventos React. Portanto, eles sempre são liberados juntos no final do evento (e você não vê o estado intermediário).

As atualizações são sempre mescladas superficialmente na ordem em que ocorrem . Portanto, se a primeira atualização for {a: 10}, a segunda for {b: 20}e a terceira for {a: 30}, o estado renderizado será {a: 30, b: 20}. A atualização mais recente para a mesma chave de estado (por exemplo, como ano meu exemplo) sempre "vence".

O this.stateobjeto é atualizado quando renderizamos novamente a interface do usuário no final do lote. Portanto, se você precisar atualizar o estado com base em um estado anterior (como incrementar um contador), use a setState(fn)versão funcional que fornece o estado anterior, em vez de ler a partir de this.state. Se você está curioso sobre o motivo disso, expliquei-o em profundidade neste comentário .


No seu exemplo, não veríamos o "estado intermediário" porque estamos dentro de um manipulador de eventos React em que o lote está ativado (porque o React "sabe" quando estamos saindo desse evento).

No entanto, nas versões React 16 e versões anteriores, ainda não há lotes por padrão fora dos manipuladores de eventos React . Portanto, se no seu exemplo tivéssemos um manipulador de resposta AJAX em vez de handleClick, cada um setState()seria processado imediatamente à medida que isso acontecesse. Neste caso, sim, você iria ver um estado intermediário:

promise.then(() => {
  // We're not in an event handler, so these are flushed separately.
  this.setState({a: true}); // Re-renders with {a: true, b: false }
  this.setState({b: true}); // Re-renders with {a: true, b: true }
  this.props.setParentState(); // Re-renders the parent
});

Percebemos que é inconveniente que o comportamento seja diferente, dependendo de você estar em um manipulador de eventos ou não . Isso será alterado em uma versão futura do React que agrupará todas as atualizações por padrão (e fornecerá uma API de aceitação para liberar as alterações de forma síncrona). Até mudarmos o comportamento padrão (potencialmente no React 17), há uma API que você pode usar para forçar o lote :

promise.then(() => {
  // Forces batching
  ReactDOM.unstable_batchedUpdates(() => {
    this.setState({a: true}); // Doesn't re-render yet
    this.setState({b: true}); // Doesn't re-render yet
    this.props.setParentState(); // Doesn't re-render yet
  });
  // When we exit unstable_batchedUpdates, re-renders once
});

Os manipuladores de eventos React internamente estão sendo unstable_batchedUpdatesagrupados e é por isso que eles são agrupados em lotes por padrão. Observe que agrupar uma atualização unstable_batchedUpdatesduas vezes não tem efeito. As atualizações são liberadas quando saímos da unstable_batchedUpdateschamada mais externa .

Essa API é "instável" no sentido de removê-la quando o lote já estiver ativado por padrão. No entanto, não o removeremos em uma versão secundária, para que você possa confiar com segurança até o React 17, se precisar forçar o lote em alguns casos fora dos manipuladores de eventos do React.


Para resumir, este é um tópico confuso, porque o React apenas faz lotes dentro de manipuladores de eventos por padrão. Isso mudará em versões futuras, e o comportamento será mais direto. Mas a solução não é agrupar menos , é agrupar mais por padrão. É isso que vamos fazer.

Dan Abramov
fonte
1
Uma maneira de "sempre acertar a ordem" é criar um objeto temporário, atribuir os diferentes valores (por exemplo obj.a = true; obj.b = true) e, no final, simplesmente fazer this.setState(obj). Isso é seguro, independentemente de você estar dentro de um manipulador de eventos ou não. Pode ser um truque interessante, se você costuma cometer o erro de definir o estado várias vezes fora dos manipuladores de eventos.
Chris
Portanto, não podemos confiar que o lote seja limitado a apenas um manipulador de eventos - como você deixou claro, pelo menos porque esse não será o caso em breve. Então devemos usar o setState com a função atualizadora para obter acesso ao estado mais recente, certo? Mas e se eu precisar usar algum state.filter para fazer um XHR para ler alguns dados e depois colocá-los em estado? Parece que vou ter que colocar um XHR com retorno de chamada adiado (e, portanto, um efeito colateral) em um atualizador. Isso é considerado uma prática recomendada, então?
Maksim Gumerov 22/02
1
E, a propósito, isso também significa que não devemos ler deste estado. a única maneira razoável de ler algum estado.X é lê-lo na função atualizador, a partir de seu argumento. E escrever para this.state também é inseguro. Então, por que permitir acesso a this.state? Talvez essas perguntas devam ser feitas separadamente, mas principalmente estou apenas tentando entender se obtive a explicação correta.
Maksim Gumerov 22/02
10
esta resposta deve ser adicionado a documentação reactjs.org
Deen John
2
Você poderia esclarecer nesta postagem se "React event handler" inclui componentDidUpdatee outros retornos de chamada do ciclo de vida a partir do React 16? Desde já, obrigado!
Ivan
6

Esta é realmente uma pergunta bastante interessante, mas a resposta não deve ser muito complicada. Há este ótimo artigo sobre suporte que tem uma resposta.

1) Se você fizer isso

this.setState({ a: true });
this.setState({ b: true });

Eu não acho que haverá uma situação onde aserá truee bserá falsepor causa do lote .

No entanto, se bfor dependentea , pode realmente haver uma situação em que você não obteria o estado esperado.

// assuming this.state = { value: 0 };
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});
this.setState({ value: this.state.value + 1});

Depois que todas as chamadas acima forem processadas this.state.value, será 1, e não 3, como você esperaria.

Isso é mencionado no artigo: setState accepts a function as its parameter

// assuming this.state = { value: 0 };
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));
this.setState((state) => ({ value: state.value + 1}));

Isso nos dará this.state.value === 3

Michal
fonte
O que acontecerá se this.state.valuefor atualizado nos manipuladores de eventos (onde setStateestá em lote) e nos retornos de chamada AJAX (onde nãosetState está em lote). Nos manipuladores de eventos, eu usaria o para sempre ter certeza de que atualizo o estado usando o estado atualmente atualizado fornecido pela função. Devo usar com uma função atualizadora dentro do código do retorno de chamada AJAX, mesmo que eu saiba que não está em lote? Você poderia esclarecer o uso de um retorno de chamada AJAX com ou sem o uso de uma função atualizadora? Obrigado! updater functionsetStatesetState
tonix 12/07/19
@ Michael, oi Michal só queria fazer mera pergunta, é verdade que se tivermos this.setState ({value: 0}); this.setState ({value: this.state.value + 1}); o primeiro setState será ignorado e apenas o segundo setState será executado?
Dickens
@ Rickens Eu acredito que ambos setStateseriam executados, mas o último venceria.
Michal
3

Várias chamadas durante o mesmo ciclo podem ser agrupadas em lote. Por exemplo, se você tentar incrementar uma quantidade de item mais de uma vez no mesmo ciclo, isso resultará no equivalente a:

Object.assign(
  previousState,
  {quantity: state.quantity + 1},
  {quantity: state.quantity + 1},
  ...
)

https://reactjs.org/docs/react-component.html

Mosè Raguzzini
fonte
3

como no doc

O setState () enfileira as alterações no estado do componente e informa ao React que esse componente e seus filhos precisam ser renderizados novamente com o estado atualizado. Este é o método principal usado para atualizar a interface do usuário em resposta aos manipuladores de eventos e respostas do servidor.

realizará a alteração como na fila ( FIFO : primeiro a entrar, primeiro a sair) a primeira chamada será a primeira a realizar

Todos
fonte
oi Ali, só queria fazer uma mera pergunta, é verdade que, se tivermos this.setState ({value: 0}); this.setState ({value: this.state.value + 1}); o primeiro setState será ignorado e apenas o segundo setState será executado?
Dickens