Cache do manipulador de eventos e melhoria no desempenho do aplicativo React

Hoje publicamos uma tradução do material, cujo autor, depois de analisar os recursos do trabalho com objetos em JavaScript, oferece aos desenvolvedores do React uma metodologia para acelerar aplicativos. Em particular, estamos falando do fato de que uma variável, que, como dizem, "recebe um objeto", e que geralmente é chamada simplesmente "objeto", na verdade, não armazena o objeto em si, mas um link para ele. As funções em JavaScript também são objetos, portanto, o que foi dito acima é verdadeiro para elas. Tendo isso em mente, o design de componentes do React e a análise crítica de seu código podem melhorar seus mecanismos internos e melhorar o desempenho do aplicativo.



Recursos de trabalho com objetos em JavaScript


Se você cria algumas funções que parecem exatamente iguais e tenta compará-las, acontece que elas, do ponto de vista do sistema, são diferentes. Para verificar isso, você pode executar o seguinte código:

const functionOne = function() { alert('Hello world!'); }; const functionTwo = function() { alert('Hello world!'); }; functionOne === functionTwo; // false 

Agora vamos tentar atribuir uma variável a uma função existente que já está atribuída a outra variável e comparar essas duas variáveis:

 const functionThree = function() { alert('Hello world!'); }; const functionFour = functionThree; functionThree === functionFour; // true 

Como você pode ver, com essa abordagem, o operador de igualdade estrita retorna true .
Objetos naturalmente se comportam da mesma maneira:

 const object1 = {}; const object2 = {}; const object3 = object1; object1 === object2; // false object1 === object3; // true 

Aqui estamos falando sobre JavaScript, mas se você tiver experiência em desenvolvimento em outros idiomas, poderá estar familiarizado com o conceito de ponteiros. No código acima, toda vez que um objeto é criado, uma parte da memória do sistema é alocada para ele. Quando usamos um comando do formulário object1 = {} , isso leva ao preenchimento com alguns dados de uma parte da memória alocada especificamente para o object1 .

É bem possível imaginar o object1 como o endereço no qual as estruturas de dados relacionadas ao objeto estão localizadas na memória. A execução do comando object2 = {} leva à alocação de outra área de memória, projetada especificamente para o object2 . obect1 e object2 na mesma área de memória? Não, cada um deles tem seu próprio enredo. É por isso que, quando tentamos comparar object1 e object2 ficamos false . Esses objetos podem ter uma estrutura idêntica, mas os endereços na memória em que estão localizados diferem e são os endereços que são verificados durante a comparação.

Executando o comando object3 = object1 , escrevemos o endereço do object1 na constante object3 . Este não é um novo objeto. Essa constante recebe o endereço de um objeto existente. Você pode verificar isso:

 const object1 = { x: true }; const object3 = object1; object3.x = false; object1.x; // false 

Neste exemplo, um objeto é criado na memória e seu endereço é gravado na constante object1 . Em seguida, o mesmo endereço é gravado na constante object3 . Alterar o object3 altera o objeto na memória. Isso significa que, ao acessar um objeto usando qualquer outra referência a ele, por exemplo, aquele armazenado no object1 , já trabalharemos com sua versão modificada.

Funções, objetos e reação


O mal-entendido do mecanismo acima por desenvolvedores iniciantes geralmente leva a erros e, talvez, a consideração dos recursos do trabalho com objetos seja digna de um artigo separado. No entanto, nosso tópico hoje é o desempenho dos aplicativos React. Nesta área, erros podem ser cometidos mesmo por desenvolvedores razoavelmente experientes que simplesmente não prestam atenção em como os aplicativos React são afetados pelo fato de que variáveis ​​e constantes JavaScript não são armazenadas nos próprios objetos, mas apenas vinculam a eles.

O que isso tem a ver com o React? O React possui mecanismos inteligentes para economizar recursos do sistema que visam melhorar o desempenho do aplicativo: se as propriedades e o estado do componente não mudarem, o que a função render não mudará. Obviamente, se o componente permanecer o mesmo, ele não precisará ser renderizado novamente. Se nada mudar, a função de render retornará a mesma de antes, portanto, não há necessidade de executá-la. Este mecanismo faz reagir rápido. Algo é exibido apenas quando necessário.

O React verifica a igualdade e as propriedades e o estado dos componentes usando os recursos JavaScript padrão, ou seja, simplesmente os compara usando o operador == . O React não realiza uma comparação “rasa” ou “profunda” de objetos para determinar sua igualdade. Uma comparação superficial é um conceito usado para descrever uma comparação de cada par de valores-chave de um objeto, em oposição a uma comparação na qual apenas os endereços dos objetos na memória são comparados (referências a eles). A comparação “profunda” de objetos vai ainda mais longe e, se os valores das propriedades comparadas dos objetos também forem objetos, eles também comparam os pares de valores-chave desses objetos. Este processo é repetido para todos os objetos aninhados em outros objetos. React não faz nada desse tipo, apenas verificando a igualdade de links.

Se, por exemplo, você alterar a propriedade de um componente representado por um objeto do formato { x: 1 } para outro objeto com a mesma aparência, o React renderizará novamente o componente, pois esses objetos estão em diferentes áreas da memória. Se você se lembrar do exemplo acima, ao alterar as propriedades de um componente de object1 para object3 , o React não renderizará novamente esse componente, pois as constantes object1 e object3 referem ao mesmo objeto.

O trabalho com funções em JavaScript é organizado exatamente da mesma maneira. Se o React encontrar os mesmos recursos cujos endereços são diferentes, ele será renderizado novamente. Se a “nova função” for apenas um link para uma função que já foi usada, não haverá nova renderização.

Um problema típico ao trabalhar com componentes


Aqui está um dos cenários de trabalho com componentes, que, infelizmente, me ocorre constantemente ao verificar o código de outra pessoa:

 class SomeComponent extends React.PureComponent { get instructions() {   if (this.props.do) {     return 'Click the button: ';   }   return 'Do NOT click the button: '; } render() {   return (     <div>       {this.instructions}       <Button onClick={() => alert('!')} />     </div>   ); } } 

Antes de nós é um componente muito simples. É um botão, quando clicado, uma notificação é exibida. Ao lado do botão, são exibidas instruções para seu uso, informando ao usuário se ele deve pressionar esse botão. Controle qual instrução será exibida definindo a propriedade do ( do={true} ou do={false} ) SomeComponent .

Sempre que o componente SomeComponent é renderizado novamente (quando o valor da propriedade do é alterado de true para false e vice-versa), o elemento Button também é renderizado. O manipulador onClick , embora seja sempre o mesmo, é recriado toda vez que a função de render é chamada. Como resultado, verifica-se que cada vez que o componente é exibido na memória, uma nova função é criada, uma vez que sua criação é realizada na função de render , um link para o novo endereço na memória é passado para <Button /> e o componente Button também é renderizado novamente, apesar do fato de que em nada mudou.

Vamos falar sobre como corrigi-lo.

Resolução de problemas


Se a função for independente do componente ( this contexto), você poderá defini-la fora do componente. Todas as instâncias do componente usarão a mesma referência de função, pois em todos os casos será a mesma função. Aqui está o que parece:

 const createAlertBox = () => alert('!'); class SomeComponent extends React.PureComponent { get instructions() {   if (this.props.do) {     return 'Click the button: ';   }   return 'Do NOT click the button: '; } render() {   return (     <div>       {this.instructions}       <Button onClick={createAlertBox} />     </div>   ); } } 

Diferente do exemplo anterior, createAlertBox , com cada chamada a render , conterá o mesmo link para a mesma área na memória. Como resultado, Button saída repetida Button não será executada.

Embora o componente Button seja pequeno e renderizado rapidamente, o problema acima associado à declaração interna de funções também pode ser encontrado em componentes grandes e complexos que levam muito tempo para renderizar. Isso pode desacelerar significativamente o aplicativo React. Nesse sentido, faz sentido seguir a recomendação, segundo a qual essas funções nunca devem ser declaradas dentro do método render .

Se a função depende do componente, ou seja, ela não pode ser definida fora dela, o método do componente pode ser passado como um manipulador de eventos:

 class SomeComponent extends React.PureComponent { createAlertBox = () => {   alert(this.props.message); }; get instructions() {   if (this.props.do) {     return 'Click the button: ';   }   return 'Do NOT click the button: '; } render() {   return (     <div>       {this.instructions}       <Button onClick={this.createAlertBox} />     </div>   ); } } 

Nesse caso, em cada instância de SomeComponent quando você clica no botão, várias mensagens serão exibidas. O manipulador de eventos do elemento Button deve ser exclusivo para SomeComponent . Ao passar o método cteateAlertBox , não importa se SomeComponent renderizado novamente. Não importa se a propriedade da message foi alterada. O endereço da função createAlertBox não muda, o que significa que o elemento Button não deve ser renderizado novamente. Graças a isso, você pode economizar recursos do sistema e melhorar a velocidade de renderização do aplicativo.

Tudo isso é bom. Mas e se as funções forem dinâmicas?

Resolvendo um problema mais complexo


O autor deste material pede que você preste atenção ao fato de que ele preparou os exemplos nesta seção, levando em consideração a primeira coisa que lhe veio à mente, adequada para ilustrar a reutilização de funções. Esses exemplos têm como objetivo ajudar o leitor a entender a essência da idéia. Embora esta seção seja recomendada para leitura para entender a essência do que está acontecendo, o autor recomenda prestar atenção aos comentários no artigo original , pois alguns leitores sugeriram versões melhores dos mecanismos discutidos aqui, que levam em consideração os recursos de invalidação de cache e mecanismos de gerenciamento de memória incorporados no React.

Portanto, é extremamente comum que em um componente existam muitos manipuladores de eventos dinâmicos exclusivos, por exemplo, algo semelhante pode ser visto no código, onde o método de matriz de map é usado no método de render :

 class SomeComponent extends React.PureComponent { render() {   return (     <ul>       {this.props.list.map(listItem =>         <li key={listItem.text}>           <Button onClick={() => alert(listItem.text)} />         </li>       )}     </ul>   ); } } 

Aqui, um número diferente de botões será exibido e um número diferente de manipuladores de eventos será criado, cada um dos quais representado por uma função exclusiva e, antecipadamente, ao criar SomeComponent , não se sabe quais serão essas funções. Como resolver este quebra-cabeça?

Aqui, a memorização nos ajudará, ou, mais simplesmente, em cache. Para cada valor exclusivo, crie uma função e coloque-a no cache. Se esse valor exclusivo ocorrer novamente, será suficiente retirar do cache a função correspondente a ele, que foi colocada anteriormente no cache.

Veja como é a implementação dessa ideia:

 class SomeComponent extends React.PureComponent { //    SomeComponent        //   . clickHandlers = {}; //       //    . getClickHandler(key) {   //       ,  .   if (!Object.prototype.hasOwnProperty.call(this.clickHandlers, key)) {     this.clickHandlers[key] = () => alert(key);   }   return this.clickHandlers[key]; } render() {   return (     <ul>       {this.props.list.map(listItem =>         <li key={listItem.text}>           <Button onClick={this.getClickHandler(listItem.text)} />         </li>       )}     </ul>   ); } } 

Cada elemento da matriz é processado pelo método getClickHandler . Este método, na primeira vez em que é chamado com um determinado valor, criará uma função exclusiva para esse valor, colocará no cache e retornará. Todas as chamadas subseqüentes a esse método, passando o mesmo valor para ele, farão com que ele simplesmente retorne um link para a função do cache.

Como resultado, a nova renderização de SomeComponent não renderiza novamente o Button . Da mesma forma, adicionar elementos à propriedade list criará dinamicamente manipuladores de eventos para cada botão.

Você precisará ser criativo na criação de identificadores exclusivos para manipuladores, se eles forem definidos por mais de uma variável, mas isso não é muito mais complicado do que a criação usual de uma propriedade de key exclusiva para cada objeto JSX obtido como resultado do método map .

Aqui, gostaria de avisar sobre possíveis problemas ao usar índices de matriz como identificadores. O fato é que, com essa abordagem, é possível encontrar erros se a ordem dos elementos na matriz for alterada ou se alguns de seus elementos forem excluídos. Portanto, por exemplo, se no início uma matriz semelhante se parecia com [ 'soda', 'pizza' ] e depois se transformou em [ 'pizza' ] , e você armazenou em cache manipuladores de eventos usando um comando do formulário listeners[0] = () => alert('soda') , você descobrirá que quando o usuário clicar no botão ao qual o manipulador com o identificador 0 está atribuído e que, de acordo com o conteúdo da matriz [ 'pizza' ] , deve exibir uma mensagem de pizza , uma mensagem de soda será exibida. Pelo mesmo motivo, não é recomendável usar índices de matriz como propriedades principais.

Sumário


Neste artigo, examinamos os recursos dos mecanismos internos do JavaScript, considerando que você pode acelerar a renderização dos aplicativos React. Esperamos que as idéias apresentadas aqui sejam úteis.

Caros leitores! Se você conhece alguma maneira interessante de otimizar os aplicativos React, compartilhe-os.

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


All Articles