React: RFClarification: porque `setState` é assíncrono?

Criado em 11 nov. 2017  ·  31Comentários  ·  Fonte: facebook/react

Por um bom tempo, tentei entender por que setState é assíncrono. E não tendo encontrado uma resposta para isso no passado, cheguei à conclusão de que era por razões históricas e provavelmente difícil de mudar agora. No entanto, @gaearon indicou que há um motivo claro, então estou curioso para descobrir :)

De qualquer forma, aqui estão as razões que ouço com frequência, mas acho que não podem ser tudo, pois são muito fáceis de contrariar

O setState assíncrono é necessário para renderização assíncrona

Muitos inicialmente pensam que é por causa da eficiência de renderização. Mas não acho que seja essa a razão por trás desse comportamento, porque manter setState sincronizado com renderização assíncrona parece trivial para mim, algo como:

Component.prototype.setState = (nextState) => {
  this.state = nextState
  if (!this.renderScheduled)
     setImmediate(this.forceUpdate)
}

Na verdade, por exemplo mobx-react permite atribuições síncronas para observáveis ​​e ainda respeita a natureza assíncrona da renderização

Async setState é necessário para saber qual estado foi _rendered_

O outro argumento que ouço às vezes é que você quer raciocinar sobre o estado que foi _rendiado_, não o estado que foi _solicitado_. Mas também não tenho certeza se esse princípio tem muito mérito. Conceitualmente, parece estranho para mim. A renderização é um efeito colateral, o estado diz respeito aos fatos. Hoje tenho 32 anos e no próximo ano farei 33, independentemente de o componente proprietário conseguir refazer a renderização este ano ou não :).

Para traçar um (provavelmente não muito bom) paralelo: Se você não fosse capaz de _ler_ sua última versão de um documento do Word escrito por você antes de imprimi-lo, isso seria muito estranho. Não creio, por exemplo, que os motores de jogo forneçam feedback sobre o estado do jogo exatamente renderizado e quais frames foram perdidos.

Uma observação interessante: em 2 anos mobx-react ninguém nunca me fez a pergunta: Como posso saber se meus observáveis ​​foram renderizados? Esta questão simplesmente não parece relevante com muita frequência.

Encontrei alguns casos em que saber quais dados foram renderizados foi relevante. O caso de que me lembro foi onde precisei saber as dimensões em pixels de alguns dados para fins de layout. Mas isso foi elegantemente resolvido usando didComponentUpdate e também não confiou em setState sendo assíncrono. Esses casos parecem tão raros que dificilmente se justifica projetar a API principalmente em torno deles. Se isso pode ser feito de alguma forma , acho que basta


Não tenho dúvidas de que a equipe React está ciente da confusão que a natureza assíncrona de setState freqüentemente apresenta, então suspeito que haja outro motivo muito bom para a semântica atual. Me diga mais :)

Discussion

Comentários muito úteis

Então, aqui estão alguns pensamentos. Esta não é uma resposta completa de forma alguma, mas talvez seja ainda mais útil do que não dizer nada.

Em primeiro lugar, acho que concordamos que atrasar a reconciliação para atualizações em lote é benéfico. Ou seja, concordamos que setState() re-renderizar de forma síncrona seria ineficiente em muitos casos, e é melhor fazer atualizações em lote se soubermos que provavelmente obteremos várias.

Por exemplo, se estamos dentro de um navegador click handler, e ambos Child e Parent chamam setState , não queremos renderizar novamente o Child duas vezes e, em vez disso, prefira marcá-los como sujos e renderizá-los novamente antes de sair do evento do navegador.

Você está perguntando: por que não podemos fazer exatamente a mesma coisa (lote), mas escrever setState updates imediatamente para this.state sem esperar o fim da reconciliação. Não acho que haja uma resposta óbvia (ambas as soluções têm compensações), mas aqui estão alguns motivos nos quais posso pensar.

Garantindo Consistência Interna

Mesmo que state seja atualizado de forma síncrona, props não é. (Você não pode saber props até que renderize novamente o componente pai e, se fizer isso de forma síncrona, o envio em lote sairá da janela.)

No momento, os objetos fornecidos pelo React ( state , props , refs ) são internamente consistentes uns com os outros. Isso significa que, se você usar apenas esses objetos, eles farão referência a uma árvore totalmente reconciliada (mesmo se for uma versão mais antiga dessa árvore). Por que isso importa?

Quando você usa apenas o estado, se ele for liberado de forma síncrona (como você propôs), este padrão funcionará:

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

No entanto, digamos que esse estado precise ser elevado para ser compartilhado entre alguns componentes, de modo que você o mova para um pai:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

Quero destacar que em aplicativos React típicos que dependem de setState() este é o tipo mais comum de refatoração específica do React que você faria diariamente .

No entanto, isso quebra nosso código!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

Isso ocorre porque, no modelo que você propôs, this.state seria liberado imediatamente, mas this.props não. E não podemos liberar this.props imediatamente sem renderizar novamente o pai, o que significa que teríamos que desistir do envio em lote (o que, dependendo do caso, pode degradar o desempenho de maneira muito significativa).

Existem também casos mais sutis de como isso pode ser interrompido, por exemplo, se você estiver misturando dados de props (ainda não liberado) e state (proposto para ser liberado imediatamente) para criar um novo estado : https://github.com/facebook/react/issues/122#issuecomment -81856416. Refs apresentam o mesmo problema: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Esses exemplos não são teóricos. Na verdade, os bindings React Redux costumavam ter exatamente esse tipo de problema porque eles misturavam os adereços React com o estado não React: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ reactjs / react-redux / pull / 99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https: // github. com / reactjs / react-redux / issues / 525.

Não sei por que os usuários do MobX não se depararam com isso, mas minha intuição é que eles podem estar esbarrando em tais cenários, mas os consideram sua própria culpa. Ou talvez eles não leiam tanto de props e, em vez disso, leiam diretamente de objetos mutáveis ​​MobX.

Então, como o React resolve isso hoje? No React, this.state e this.props atualizados somente após a reconciliação e limpeza, então você veria 0 sendo impressos antes e depois da refatoração. Isso torna o levantamento do estado seguro.

Sim, isso pode ser inconveniente em alguns casos. Especialmente para pessoas que vêm de origens mais OO que querem apenas mudar o estado várias vezes, em vez de pensar em como representar uma atualização de estado completa em um único lugar. Posso ter empatia com isso, embora eu ache que manter as atualizações de estado concentradas é mais claro do ponto de vista de depuração: https://github.com/facebook/react/issues/122#issuecomment -19888472.

Ainda assim, você tem a opção de mover o estado que deseja ler imediatamente para algum objeto mutável lateralmente, especialmente se não o usar como uma fonte de verdade para renderização. O que é basicamente o que o MobX permite que você faça 🙂.

Você também tem a opção de limpar a árvore inteira se souber o que está fazendo. A API é chamada de ReactDOM.flushSync(fn) . Não acho que tenhamos documentado ainda, mas com certeza faremos isso em algum ponto durante o ciclo de lançamento de 16.x. Observe que, na verdade, ele força uma nova renderização completa para atualizações que acontecem dentro da chamada, portanto, você deve usá-lo com moderação. Desta forma, não quebra a garantia de consistência interna entre props , state e refs .

Para resumir, o modelo React nem sempre leva ao código mais conciso, mas é internamente consistente e garante que o levantamento do estado seja seguro .

Habilitando atualizações simultâneas

Conceitualmente, o React se comporta como se tivesse uma única fila de atualização por componente. É por isso que a discussão faz sentido: discutimos se devemos aplicar as atualizações a this.state imediatamente ou não, porque não temos dúvidas de que as atualizações serão aplicadas nessa ordem exata. No entanto, esse não precisa ser o caso ( haha ).

Recentemente, temos falado muito sobre “renderização assíncrona”. Admito que não fizemos um trabalho muito bom em comunicar o que isso significa, mas essa é a natureza da P&D: você vai atrás de uma ideia que parece conceitualmente promissora, mas você realmente entende suas implicações somente depois de ter passado bastante tempo com ela.

Uma maneira que explicamos a “renderização assíncrona” é que o React pode atribuir diferentes prioridades a setState() chamadas, dependendo de onde elas vêm: um manipulador de eventos, uma resposta de rede, uma animação, etc.

Por exemplo, se você estiver digitando uma mensagem, setState() chamadas no componente TextBox precisam ser liberadas imediatamente. No entanto, se você receber uma nova mensagem enquanto está digitando , provavelmente é melhor atrasar a renderização do novo MessageBubble até um certo limite (por exemplo, um segundo) do que deixar a digitação falhar devido ao bloqueio do fio.

Se permitirmos que certas atualizações tenham “prioridade mais baixa”, poderemos dividir sua renderização em pequenos pedaços de alguns milissegundos para que não sejam perceptíveis para o usuário.

Sei que otimizações de desempenho como essa podem não parecer muito empolgantes ou convincentes. Você poderia dizer: “não precisamos disso com MobX, nosso rastreamento de atualização é rápido o suficiente para evitar apenas re-renderizações”. Não acho que seja verdade em todos os casos (por exemplo, não importa o quão rápido o MobX seja, você ainda precisa criar nós DOM e fazer a renderização para visualizações recém-montadas). Ainda assim, se fosse verdade, e se você conscientemente decidiu que está tudo bem em sempre agrupar objetos em uma biblioteca JavaScript específica que rastreia leituras e gravações, talvez você não se beneficie tanto dessas otimizações.

Mas a renderização assíncrona não se trata apenas de otimizações de desempenho.

Por exemplo, considere o caso em que você está navegando de uma tela para outra. Normalmente, você mostraria um botão giratório enquanto a nova tela está sendo renderizada.

No entanto, se a navegação for rápida o suficiente (dentro de um segundo ou mais), piscar e ocultar imediatamente um botão giratório causa uma degradação da experiência do usuário. Pior, se você tiver vários níveis de componentes com dependências assíncronas diferentes (dados, código, imagens), você acaba com uma cascata de botões giratórios que piscam brevemente um por um. Isso é visualmente desagradável e torna seu aplicativo mais lento na prática por causa de todos os refluxos de DOM. É também a fonte de muitos códigos clichê.

Não seria bom se, ao fazer um setState() simples que renderiza uma visão diferente, pudéssemos “iniciar” a renderização da visão atualizada “em segundo plano”? Imagine que, sem escrever nenhum código de coordenação, você poderia escolher mostrar um spinner se a atualização demorasse mais do que um determinado limite (por exemplo, um segundo) e, de outra forma, deixar o React realizar uma transição contínua quando as dependências assíncronas de toda a nova subárvore forem satisfeito . Além disso, enquanto estamos “esperando”, a “tela antiga” permanece interativa (por exemplo, para que você possa escolher um item diferente para a transição), e o React garante que, se demorar muito, você terá que mostrar um botão giratório.

Acontece que, com o modelo React atual e alguns ajustes nos ciclos de vida , podemos realmente implementar isso! @acdlite tem trabalhado nesse recurso nas últimas semanas e postará um RFC para ele em breve.

Observe que isso só é possível porque this.state não é descarregado imediatamente. Se fosse liberado imediatamente, não teríamos como começar a renderizar uma “nova versão” da visualização em segundo plano enquanto a “versão antiga” ainda estivesse visível e interativa. Suas atualizações de estado independentes entrariam em conflito.

Eu não quero roubar o trovão de @acdlite no que diz respeito a anunciar tudo isso, mas espero que isso soe pelo menos um pouco emocionante. Eu entendo que isso ainda pode soar como vaporware, ou como se não soubéssemos realmente o que estamos fazendo. Espero que possamos convencê-lo do contrário nos próximos meses, e que você aprecie a flexibilidade do modelo React. E, tanto quanto eu entendo, pelo menos em parte essa flexibilidade é possível graças a não liberar as atualizações de estado imediatamente.

Todos 31 comentários

Estamos todos esperando @gaearon .

@Kaybarax Ei, é fim de semana ;-)

@mweststrate Oh! foi mal. Legal.

Vou arriscar aqui e dizer que é por causa do lote de vários setState s no mesmo carrapato.

Vou sair de férias na próxima semana, mas provavelmente irei na terça, então tentarei responder na segunda.

function enqueueUpdate (componente) {
garantirInjected ();

// Várias partes do nosso código (como ReactCompositeComponent's
// _renderValidatedComponent) assume que as chamadas para renderizar não estão aninhadas;
// verifique se é esse o caso. (Isso é chamado por cada atualização de nível superior
// função, como setState, forceUpdate, etc .; criação e
// a destruição dos componentes de nível superior é protegida no ReactMount.)

if (! batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates (enqueueUpdate, componente);
Retorna;
}

dirtyComponents.push (componente);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}

@mweststrate apenas 2 centavos: essa é uma pergunta muito válida.
Tenho certeza de que todos concordamos que seria muito mais fácil raciocinar sobre o estado se setState fosse síncrono.
Quaisquer que tenham sido os motivos para tornar setState assíncrono, não tenho certeza de que reagir bem à equipe em comparação com as desvantagens que isso introduziria, por exemplo, a dificuldade de raciocinar sobre o estado agora e a confusão que isso traz para os desenvolvedores.

Eu pessoalmente tive e vi em outros desenvolvedores confusão sobre este assunto. @gaearon , seria ótimo obter uma explicação para isso quando você tiver algum tempo :)

Desculpe, é o final do ano e estamos um pouco atrasados ​​no GitHub etc. tentando encerrar tudo o que estávamos trabalhando antes das férias.

Tenho a intenção de voltar a este tópico e discuti-lo. Mas também é um alvo móvel porque estamos trabalhando atualmente em recursos do React assíncronos que se relacionam diretamente a como e quando this.state é atualizado. Não quero perder muito tempo escrevendo algo e depois ter que reescrevê-lo porque as suposições subjacentes mudaram. Então, gostaria de manter isso em aberto, mas ainda não sei quando poderei dar uma resposta definitiva.

Então, aqui estão alguns pensamentos. Esta não é uma resposta completa de forma alguma, mas talvez seja ainda mais útil do que não dizer nada.

Em primeiro lugar, acho que concordamos que atrasar a reconciliação para atualizações em lote é benéfico. Ou seja, concordamos que setState() re-renderizar de forma síncrona seria ineficiente em muitos casos, e é melhor fazer atualizações em lote se soubermos que provavelmente obteremos várias.

Por exemplo, se estamos dentro de um navegador click handler, e ambos Child e Parent chamam setState , não queremos renderizar novamente o Child duas vezes e, em vez disso, prefira marcá-los como sujos e renderizá-los novamente antes de sair do evento do navegador.

Você está perguntando: por que não podemos fazer exatamente a mesma coisa (lote), mas escrever setState updates imediatamente para this.state sem esperar o fim da reconciliação. Não acho que haja uma resposta óbvia (ambas as soluções têm compensações), mas aqui estão alguns motivos nos quais posso pensar.

Garantindo Consistência Interna

Mesmo que state seja atualizado de forma síncrona, props não é. (Você não pode saber props até que renderize novamente o componente pai e, se fizer isso de forma síncrona, o envio em lote sairá da janela.)

No momento, os objetos fornecidos pelo React ( state , props , refs ) são internamente consistentes uns com os outros. Isso significa que, se você usar apenas esses objetos, eles farão referência a uma árvore totalmente reconciliada (mesmo se for uma versão mais antiga dessa árvore). Por que isso importa?

Quando você usa apenas o estado, se ele for liberado de forma síncrona (como você propôs), este padrão funcionará:

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

No entanto, digamos que esse estado precise ser elevado para ser compartilhado entre alguns componentes, de modo que você o mova para um pai:

-this.setState({ value: this.state.value + 1 });
+this.props.onIncrement(); // Does the same thing in a parent

Quero destacar que em aplicativos React típicos que dependem de setState() este é o tipo mais comum de refatoração específica do React que você faria diariamente .

No entanto, isso quebra nosso código!

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

Isso ocorre porque, no modelo que você propôs, this.state seria liberado imediatamente, mas this.props não. E não podemos liberar this.props imediatamente sem renderizar novamente o pai, o que significa que teríamos que desistir do envio em lote (o que, dependendo do caso, pode degradar o desempenho de maneira muito significativa).

Existem também casos mais sutis de como isso pode ser interrompido, por exemplo, se você estiver misturando dados de props (ainda não liberado) e state (proposto para ser liberado imediatamente) para criar um novo estado : https://github.com/facebook/react/issues/122#issuecomment -81856416. Refs apresentam o mesmo problema: https://github.com/facebook/react/issues/122#issuecomment -22659651.

Esses exemplos não são teóricos. Na verdade, os bindings React Redux costumavam ter exatamente esse tipo de problema porque eles misturavam os adereços React com o estado não React: https://github.com/reactjs/react-redux/issues/86 , https://github.com/ reactjs / react-redux / pull / 99 , https://github.com/reactjs/react-redux/issues/292 , https://github.com/reactjs/redux/issues/1415 , https: // github. com / reactjs / react-redux / issues / 525.

Não sei por que os usuários do MobX não se depararam com isso, mas minha intuição é que eles podem estar esbarrando em tais cenários, mas os consideram sua própria culpa. Ou talvez eles não leiam tanto de props e, em vez disso, leiam diretamente de objetos mutáveis ​​MobX.

Então, como o React resolve isso hoje? No React, this.state e this.props atualizados somente após a reconciliação e limpeza, então você veria 0 sendo impressos antes e depois da refatoração. Isso torna o levantamento do estado seguro.

Sim, isso pode ser inconveniente em alguns casos. Especialmente para pessoas que vêm de origens mais OO que querem apenas mudar o estado várias vezes, em vez de pensar em como representar uma atualização de estado completa em um único lugar. Posso ter empatia com isso, embora eu ache que manter as atualizações de estado concentradas é mais claro do ponto de vista de depuração: https://github.com/facebook/react/issues/122#issuecomment -19888472.

Ainda assim, você tem a opção de mover o estado que deseja ler imediatamente para algum objeto mutável lateralmente, especialmente se não o usar como uma fonte de verdade para renderização. O que é basicamente o que o MobX permite que você faça 🙂.

Você também tem a opção de limpar a árvore inteira se souber o que está fazendo. A API é chamada de ReactDOM.flushSync(fn) . Não acho que tenhamos documentado ainda, mas com certeza faremos isso em algum ponto durante o ciclo de lançamento de 16.x. Observe que, na verdade, ele força uma nova renderização completa para atualizações que acontecem dentro da chamada, portanto, você deve usá-lo com moderação. Desta forma, não quebra a garantia de consistência interna entre props , state e refs .

Para resumir, o modelo React nem sempre leva ao código mais conciso, mas é internamente consistente e garante que o levantamento do estado seja seguro .

Habilitando atualizações simultâneas

Conceitualmente, o React se comporta como se tivesse uma única fila de atualização por componente. É por isso que a discussão faz sentido: discutimos se devemos aplicar as atualizações a this.state imediatamente ou não, porque não temos dúvidas de que as atualizações serão aplicadas nessa ordem exata. No entanto, esse não precisa ser o caso ( haha ).

Recentemente, temos falado muito sobre “renderização assíncrona”. Admito que não fizemos um trabalho muito bom em comunicar o que isso significa, mas essa é a natureza da P&D: você vai atrás de uma ideia que parece conceitualmente promissora, mas você realmente entende suas implicações somente depois de ter passado bastante tempo com ela.

Uma maneira que explicamos a “renderização assíncrona” é que o React pode atribuir diferentes prioridades a setState() chamadas, dependendo de onde elas vêm: um manipulador de eventos, uma resposta de rede, uma animação, etc.

Por exemplo, se você estiver digitando uma mensagem, setState() chamadas no componente TextBox precisam ser liberadas imediatamente. No entanto, se você receber uma nova mensagem enquanto está digitando , provavelmente é melhor atrasar a renderização do novo MessageBubble até um certo limite (por exemplo, um segundo) do que deixar a digitação falhar devido ao bloqueio do fio.

Se permitirmos que certas atualizações tenham “prioridade mais baixa”, poderemos dividir sua renderização em pequenos pedaços de alguns milissegundos para que não sejam perceptíveis para o usuário.

Sei que otimizações de desempenho como essa podem não parecer muito empolgantes ou convincentes. Você poderia dizer: “não precisamos disso com MobX, nosso rastreamento de atualização é rápido o suficiente para evitar apenas re-renderizações”. Não acho que seja verdade em todos os casos (por exemplo, não importa o quão rápido o MobX seja, você ainda precisa criar nós DOM e fazer a renderização para visualizações recém-montadas). Ainda assim, se fosse verdade, e se você conscientemente decidiu que está tudo bem em sempre agrupar objetos em uma biblioteca JavaScript específica que rastreia leituras e gravações, talvez você não se beneficie tanto dessas otimizações.

Mas a renderização assíncrona não se trata apenas de otimizações de desempenho.

Por exemplo, considere o caso em que você está navegando de uma tela para outra. Normalmente, você mostraria um botão giratório enquanto a nova tela está sendo renderizada.

No entanto, se a navegação for rápida o suficiente (dentro de um segundo ou mais), piscar e ocultar imediatamente um botão giratório causa uma degradação da experiência do usuário. Pior, se você tiver vários níveis de componentes com dependências assíncronas diferentes (dados, código, imagens), você acaba com uma cascata de botões giratórios que piscam brevemente um por um. Isso é visualmente desagradável e torna seu aplicativo mais lento na prática por causa de todos os refluxos de DOM. É também a fonte de muitos códigos clichê.

Não seria bom se, ao fazer um setState() simples que renderiza uma visão diferente, pudéssemos “iniciar” a renderização da visão atualizada “em segundo plano”? Imagine que, sem escrever nenhum código de coordenação, você poderia escolher mostrar um spinner se a atualização demorasse mais do que um determinado limite (por exemplo, um segundo) e, de outra forma, deixar o React realizar uma transição contínua quando as dependências assíncronas de toda a nova subárvore forem satisfeito . Além disso, enquanto estamos “esperando”, a “tela antiga” permanece interativa (por exemplo, para que você possa escolher um item diferente para a transição), e o React garante que, se demorar muito, você terá que mostrar um botão giratório.

Acontece que, com o modelo React atual e alguns ajustes nos ciclos de vida , podemos realmente implementar isso! @acdlite tem trabalhado nesse recurso nas últimas semanas e postará um RFC para ele em breve.

Observe que isso só é possível porque this.state não é descarregado imediatamente. Se fosse liberado imediatamente, não teríamos como começar a renderizar uma “nova versão” da visualização em segundo plano enquanto a “versão antiga” ainda estivesse visível e interativa. Suas atualizações de estado independentes entrariam em conflito.

Eu não quero roubar o trovão de @acdlite no que diz respeito a anunciar tudo isso, mas espero que isso soe pelo menos um pouco emocionante. Eu entendo que isso ainda pode soar como vaporware, ou como se não soubéssemos realmente o que estamos fazendo. Espero que possamos convencê-lo do contrário nos próximos meses, e que você aprecie a flexibilidade do modelo React. E, tanto quanto eu entendo, pelo menos em parte essa flexibilidade é possível graças a não liberar as atualizações de estado imediatamente.

Maravilhosa explicação detalhada para as decisões por trás da arquitetura do React. Obrigado.

marca

Obrigada, Dan.

Eu sou esse problema. Pergunta incrível e resposta incrível. Sempre pensei que era uma má decisão de design, agora tenho que repensar 😄

Obrigada, Dan.

Eu chamo isso de asyncAwesome setState: smile:

Tendo a pensar que tudo deve ser implementado de forma assíncrona primeiro, e se você achar que uma operação de sincronização precisa ser concluída, encerre a operação assíncrona com uma espera pela conclusão. É muito mais fácil fazer o código de sincronização a partir do código assíncrono (tudo o que você precisa é um wrapper) do que o inverso (o que basicamente requer uma reescrita completa, a menos que você alcance o encadeamento, que não é nada leve).

@gaearon, obrigado pela explicação extensa! Isso já me incomoda há muito tempo ("deve haver um bom motivo, mas ninguém sabe qual"). Mas agora faz todo o sentido e vejo como esta é uma decisão realmente consciente :). Muito obrigado pela resposta extensa, realmente agradeço!

Ou talvez eles não leiam tanto nos adereços e, em vez disso, leiam diretamente nos objetos mutáveis ​​do MobX.

Acho que isso é bem verdade, em MobX os adereços são normalmente usados ​​apenas como configuração de componente, e os dados de domínio normalmente não são capturados em adereços, mas em entidades de domínio que são passadas entre os componentes.

Mais uma vez, muito obrigado!

@gaearon Obrigado pela explicação detalhada e excelente.
Embora ainda haja algo faltando aqui, que acho que entendo, mas quero ter certeza.

Quando o evento é registrado "Outside React", isso significa talvez através de addEventListener em um ref por exemplo. Então, não há lote ocorrendo.
Considere este código:

class App extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }

  componentDidMount() {
    this.refBtn.addEventListener("click", this.onClick);
  }

  componentWillUnmount() {
    this.refBtn.removeEventListener("click", this.onClick);
  }

  onClick = () => {
    console.log("before setState", this.state.count);
    this.setState(state => ({ count: state.count + 1 }));
    console.log("after setState", this.state.count);
  };

  render() {
    return (
      <div>
        <button onClick={this.onClick}>React Event</button>
        <button ref={ref => (this.refBtn = ref)}>Direct DOM event</button>
      </div>
    );
  }
}

Quando clicarmos no botão "React Event", veremos no console:
"before setState" 0
"after setState" 0
Quando o outro botão "evento DOM direto" for clicado, veremos no console:
"before setState" 0
"after setState" 1

Depois de algumas pesquisas e navegar pelo código-fonte, acho que sei por que isso acontece.
react não pode controlar totalmente o fluxo do evento e não pode ter certeza de quando e como o próximo evento será disparado, então, como um "modo de pânico", ele apenas acionará a mudança de estado imediatamente.

Quais são seus pensamentos sobre isso? :pensando:

@ sag1v embora um pouco relacionado, é provavelmente mais claro abrir um novo fascículo para novas questões. Basta usar # 11527 em algum lugar da descrição para vinculá-lo a este.

@ sag1v @gaearon me deu uma resposta muito concisa aqui https://twitter.com/dan_abramov/status/949992957180104704 . Acho que sua opinião sobre isso também me responderia de forma mais concreta.

@mweststrate Pensei em abrir um novo problema, mas percebi que isso está diretamente relacionado à sua pergunta "por que setState assíncrono?".
Como esta discussão é sobre as decisões tomadas em fazer setState "async", pensei em adicionar quando e por que torná-lo "sincronizado".
Não me importo de abrir um novo problema se não te convencer de que minha postagem está relacionada a esse problema: wink:

@Kaybarax Isso porque sua pergunta foi "_Quando está sincronizando_" e não "_ Por que está sincronizando_" ?.
Como mencionei no meu post, acho que sei o porquê, mas quero ter certeza e obter a resposta oficial hehe. :sorriso:

O react não pode controlar totalmente o fluxo do evento e não pode ter certeza de quando e como o próximo evento será disparado, então como um "modo de pânico" ele irá apenas disparar a mudança de estado imediatamente

Tipo de. Embora isso não esteja exatamente relacionado à questão sobre a atualização de this.state .

O que você está perguntando é em quais casos o React habilita o envio em lote. Atualmente, o React envia atualizações em lote dentro dos manipuladores de eventos gerenciados pelo React porque o React “fica” no topo da estrutura da pilha de chamadas e sabe quando todos os manipuladores de eventos React foram executados. Nesse ponto, ele libera a atualização.

Se o manipulador de eventos não for configurado pelo React, atualmente ele torna a atualização síncrona. Porque não sabe se é seguro esperar ou não e se outras atualizações acontecerão em breve.

Nas versões futuras do React, esse comportamento mudará. O plano é tratar as atualizações como baixa prioridade por padrão, de forma que elas acabem sendo agrupadas e agrupadas (por exemplo, em um segundo), com uma opção de liberá-las imediatamente. Você pode ler mais aqui: https://github.com/facebook/react/issues/11171#issuecomment -357945371.

Impressionante!

Essa pergunta e resposta devem ser documentadas em algum lugar mais acessível. Obrigado galera nos iluminando.

Aprendeu muito . Obrigado

Tentando adicionar meu ponto de vista ao tópico. Trabalho em um aplicativo baseado em MobX há alguns meses, estou explorando ClojureScript há anos e fiz minha própria alternativa React (chamada Respo), tentei Redux nos primeiros dias embora muito pouco tempo, e estou apostando no ReasonML.

A ideia central em combinar React e Functional Programming (FP) é que você tem um pedaço de dados que pode renderizar em uma visualização com quaisquer habilidades que você possua e que obedeçam às leis em FP. Você não tem efeitos colaterais se usar apenas funções puras.

React não é puramente funcional. Ao adotar os estados locais dentro dos componentes, o React tem o poder de interagir com várias bibliotecas relacionadas ao DOM e outras APIs de navegador (também amigáveis ​​ao MobX), o que, por sua vez, torna o React impuro. No entanto, tentei no ClojureScript, se React for puro, pode ser um desastre, pois é realmente difícil interagir com tantas bibliotecas existentes que têm efeitos colaterais nelas.

Portanto, no Respo (minha própria solução), eu tinha dois objetivos que parecem conflitantes: 1) view = f(store) portanto, nenhum estado local é esperado; 2) Não gosto de programar todos os estados de interface do usuário do componente em redutores globais, pois isso pode ser difícil de manter. No final, descobri que preciso de um açúcar de sintaxe, que me ajuda a manter os estados dos componentes em um armazenamento global com paths , enquanto escrevo atualizações de estado dentro do componente com macros Clojure.

Então, o que aprendi: estados locais são um recurso de experiência do desenvolvedor, no fundo queremos estados globais que permitem que nossos mecanismos executem otimizações em níveis profundos. Então, o pessoal do MobX prefere OOP, isso é para experiência de desenvolvedor ou para engines?

A propósito, dei uma palestra sobre o futuro do React e seus recursos assíncronos, caso você tenha perdido:
https://reactjs.org/blog/2018/03/01/sneak-peek-beyond-react-16.html

Dan, você é meu ídolo ... muito obrigado.

Esta página foi útil?
0 / 5 - 0 avaliações

Questões relacionadas

brunolemos picture brunolemos  ·  285Comentários

gaearon picture gaearon  ·  227Comentários

robdodson picture robdodson  ·  129Comentários

addyosmani picture addyosmani  ·  143Comentários

gaearon picture gaearon  ·  104Comentários