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