Como tornar um aplicativo React mais rápido com a co-hospedagem do estado

O artigo é sobre a colocação do estado , ou seja, sobre a colocação conjunta dos estados , esse termo ainda pode ser traduzido como colocação do estado ou colocação do estado .


Um dos principais motivos para desacelerar um aplicativo React é o seu estado global. Mostrarei isso com um exemplo de aplicação muito simples, após o qual darei um exemplo mais próximo da vida real.


Aqui está um aplicativo simples no qual você pode inserir um nome para o cachorro (se a janela não funcionar, aqui está o link ):



Se você jogar com este aplicativo, em breve descobrirá que ele funciona muito lentamente. Ao interagir com qualquer campo de entrada, há problemas visíveis de desempenho. Em tal situação, você pode usar uma bóia salva-vidas na forma de React.memo e envolvê-la com todos os componentes com uma renderização lenta. Mas vamos tentar resolver esse problema de maneira diferente.


Aqui está o código para este aplicativo:


 function sleep(time) { const done = Date.now() + time while (done > Date.now()) { // ... } } //      // -       function SlowComponent({time, onChange}) { sleep(time) return ( <div> Wow, that was{' '} <input value={time} type="number" onChange={e => onChange(Number(e.target.value))} /> ms slow </div> ) } function DogName({time, dog, onChange}) { return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => onChange(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } function App() { //   " " (global state) const [dog, setDog] = React.useState('') const [time, setTime] = React.useState(200) return ( <div> <DogName time={time} dog={dog} onChange={setDog} /> <SlowComponent time={time} onChange={setTime} /> </div> ) } 

Se você não leu o artigo sobre Colocation (co-localização), sugiro que você o leia. Sabendo que a co-hospedagem pode facilitar o trabalho com nosso aplicativo, vamos usar esse princípio ao trabalhar com estados.


Preste atenção ao código do nosso aplicativo, ou seja, o estado do time - ele é usado por cada componente do aplicativo, portanto, foi elevado ( elevando - elevando o estado ) ao componente App (o componente que envolve todo o aplicativo). No entanto, o estado "dog" ( dog e setDog ) é usado por apenas um componente, não é necessário no componente App , então vamos movê-lo para o componente DogName :


 function DogName({time}) { // <-    const [dog, setDog] = React.useState('') // <-   return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => setDog(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } function App() { //   " " (global state) const [time, setTime] = React.useState(200) return ( <div> <DogName time={time} /> // <-    <SlowComponent time={time} onChange={setTime} /> </div> ) } 

E aqui está o nosso resultado (se a janela não funcionar, link ):



Uau! Digitar um nome agora funciona MUITO mais rápido. Além disso, o componente ficou mais fácil de manter, graças à co-localização. Mas por que funcionou mais rápido?


Eles dizem que a melhor maneira de fazer algo rapidamente é fazer o mínimo possível de coisas. É exatamente o que está acontecendo aqui. Quando gerenciamos o estado localizado na parte superior da árvore de componentes, cada atualização desse estado invalida a árvore inteira. A reação não sabe o que mudou, por isso, ele precisa verificar todos os componentes para ver se precisam de atualizações do DOM. Esse processo não é gratuito e consome recursos (especialmente se você tiver componentes intencionalmente lentos). Mas se você mover o estado o mais baixo possível na árvore de componentes, como fizemos com o estado do dog e o componente DogName , o React fará menos verificações. O React não verificará o componente SlowComponent (que SlowComponent deliberadamente), pois o React sabe que esse componente ainda não pode afetar a saída.


Em suma, anteriormente, ao alterar o nome do cão, cada componente era verificado quanto a alterações (renderizadas novamente). E após as alterações que fizemos no código, o React começou a verificar apenas o componente DogName . Isso levou a um aumento acentuado da produtividade!


Na vida real


Vejo que os desenvolvedores estão colocando no repositório global Redux ou no contexto global o que realmente não deveria ser global. DogName como o DogName do exemplo acima geralmente são locais onde ocorre um problema de desempenho. Vejo frequentemente que esse problema se manifesta ao interagir com o mouse (por exemplo, ao exibir uma dica de ferramenta acima de um gráfico ou acima de uma tabela de dados).


Uma solução para esse problema é "cancelar" a interação do usuário (ou seja, aguardamos até que o usuário pare de digitar e aplicamos a atualização de estado). Às vezes, é o melhor que podemos fazer, mas pode levar a uma experiência ruim do usuário (o modo simultâneo futuro deve minimizar a necessidade de fazer isso . Veja esta demonstração de Dan Abramov ).


Outra solução que os desenvolvedores costumam usar é usar uma das renderizações de resgate do React, por exemplo, React.memo . Isso funcionará muito bem em nosso exemplo rebuscado, pois permite que o React pule a nova renderização do SlowComponent , mas, na prática, o aplicativo pode sofrer “morte de mil cortes”, pois em um aplicativo real, o abrandamento geralmente não é devido a um lento componente, e devido ao trabalho insuficientemente rápido de muitos componentes, você deve usar o React.memo em React.memo lugar. Feito isso, você terá que começar a usar useMemo e useCallback , caso contrário, todo o trabalho que você colocar no React.memo será em vão. Essas ações podem resolver o problema, mas aumentam significativamente a complexidade do seu código e, de fato, ainda são menos eficazes do que compartilhar estados, pois o React precisa passar por cada componente (começando do topo) para determinar se precisa ser renderizado novamente.


Se você quiser brincar com um exemplo um pouco menos absurdo , vá para codesandbox aqui .


O que é co-localização?


O princípio da colocação conjunta afirma:


O código deve estar localizado o mais próximo possível do local a que se refere.

Portanto, para cumprir esse princípio, nosso estado de dog deve estar dentro do componente DogName :


 function DogName({time}) { const [dog, setDog] = React.useState('') return ( <div> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => setDog(e.target.value)} /> <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> </div> ) } 

Mas e se quebrarmos esse componente em vários componentes? Para onde deveria ir o estado? A resposta é a mesma: "o mais próximo possível do local a que se refere", e esse será o componente pai comum mais próximo . Como exemplo, vamos dividir o componente DogName que a input p exibida em diferentes componentes:


 function DogName({time}) { const [dog, setDog] = React.useState('') return ( <div> <DogInput dog={dog} onChange={setDog} /> <DogFavoriteNumberDisplay time={time} dog={dog} /> </div> ) } function DogInput({dog, onChange}) { return ( <> <label htmlFor="dog">Dog Name</label> <br /> <input id="dog" value={dog} onChange={e => onChange(e.target.value)} /> </> ) } function DogFavoriteNumberDisplay({time, dog}) { return ( <p> {dog ? `${dog}'s favorite number is ${time}.` : 'enter a dog name'} </p> ) } 

Não podemos mover o estado para o componente DogInput , porque o componente DogFavoriteNumberDisplay também precisa de acesso ao estado, portanto, movemos da parte inferior para a parte superior da árvore de componentes até encontrarmos o elemento pai comum desses dois componentes e organizarmos o gerenciamento de estado nele.


Tudo isso se aplica a situações em que você precisa controlar o estado de dezenas de componentes em uma tela específica do seu aplicativo. Você pode até mover isso para o contexto para evitar a perfuração de suporte, se desejar. Mas mantenha esse contexto o mais próximo possível do local ao qual ele pertence e, em seguida, você continuará mantendo o bom desempenho (e a usabilidade do código) fornecido pelo compartilhamento. Lembre-se de que você não precisa colocar todos os contextos no nível superior do seu aplicativo React. Mantenha-os onde faz mais sentido.


Esta é a ideia principal do meu outro post, Application State Management with React . Mantenha seus estados o mais próximo possível do local em que são usados, isso melhorará o desempenho e a usabilidade do código. Com essa abordagem, a única coisa que possivelmente piorará o desempenho do seu aplicativo são interações especialmente complexas com os elementos da interface.


Então, o que usar, contextos ou Redux?


Se você ler "Um truque simples para otimizar as novas renderizações do React" , saberá que pode garantir que apenas os componentes que usam o estado alterado sejam atualizados. Dessa forma, você pode solucionar esse problema. Mas as pessoas ainda enfrentam problemas de desempenho ao usar o Editor. O problema é que o React-Redux espera que você siga suas recomendações para evitar a renderização desnecessária dos componentes conectados . Sempre há uma chance de erro; você pode configurar acidentalmente componentes para que eles comecem a renderizar com frequência quando outros estados globais forem alterados. E quanto maior o seu aplicativo, mais negativo será o efeito, especialmente se você adicionar muitos estados ao Editor.


Existem métodos que podem ajudá-lo a evitar esses problemas, por exemplo, use memoized Reselect mapState ou leia a documentação do Editor para obter mais informações sobre como melhorar o desempenho do aplicativo .


Vale ressaltar que a co-colocação pode ser aplicada em conjunto com o Editor. Apenas use Editores apenas para estados globais e, para todo o resto, use co-localização. Existem algumas regras úteis nas Perguntas frequentes do editor para ajudar você a decidir se o estado deve funcionar no Editor ou se deve permanecer no componente .


A propósito, se você dividir seu estado em domínios (usando vários contextos, dependendo do domínio), o problema será menos pronunciado.


Mas o fato permanece: a co-localização de estados reduz os problemas de desempenho e simplifica a manutenção do código.


Decida onde colocar o estado


Árvore de decisão:


onde colocar


Versão em texto, se não houver como ver a figura:


  • 1 Começamos o desenvolvimento do aplicativo. Vá para 2
  • 2 Status no componente. Ir para 3
  • 3 A condição é usada apenas por este componente?
    • Hein? Vamos para 4
    • Não? Esse estado precisa de apenas um componente filho?
    • Hein? Mova-o para este componente filho (use co-location). Vamos para o 3.
    • Não? Esse estado precisa de componentes pais ou vizinhos (componentes "fraternos", isto é, filhos do mesmo componente pai)?
      • Hein? Mova o estado acima para o componente pai. Ir para 3
      • Não? Vamos para 4
  • 4 Deixe como está. Ir para 5
  • 5 Existe um problema com a perfuração de suporte?
    • Hein? Movemos esse estado para o provedor de contexto e o renderizamos no componente em que o estado está sendo gerenciado. Vá para 6
    • Não? Vá para 6
  • 6 Nós enviamos o aplicativo. Quando novos requisitos aparecerem, vá para 1

É importante que esse processo faça parte do processo regular de refatoração / manutenção de aplicativos. Se sua condição não for levantada onde deveria ser levantada, esse estado deixará de funcionar corretamente e você perceberá. No entanto, se você não seguir o método de co-localização de estados e não diminuir os estados mais baixos na hierarquia de componentes, seu aplicativo continuará funcionando. Por esse motivo, você não notará imediatamente problemas de desempenho e capacidade de gerenciamento que se acumularão gradualmente.


Conclusão


Em geral, os desenvolvedores entendem muito bem quando é necessário elevar o estado ("estado de elevação") quando necessário, mas não entendemos bem quando o estado precisa ser baixado. Sugiro que você dê uma olhada no código do seu aplicativo e pense em onde o estado pode ser omitido aplicando o princípio de "co-localização". Pergunte a si mesmo: "Preciso do estado isOpen de uma janela modal no Editor?" (a resposta provavelmente não).


Use o princípio da co-localização e seu código se tornará mais fácil e rápido.


Boa sorte

Source: https://habr.com/ru/post/pt485032/


All Articles