Desmistificando o limiar entre JavaScript e React
Só sei que é assim agora é passado! vamos conhecer melhor esses recursos e
como eles podem influenciar na performance quando você desenvolve aplicações em
React.
Em um componente React você pode definir métodos de classe e então os utilizar
no método render
de seu componente. Exemplo:
class
extends React.Component {
() {
console.dir(this); // "this" é quem vai chamar o método risada
return "HAHAHAHA!!!";
}
render() {
return
;
}
}
ReactDOM.render(, document.getElementById('root'));
Nesse exemplo utilizei o método risada
dentro do método render
como
this.risada
porque dentro do render
, a palavra-chave this
refere-se à
instância do componente associado com o elemento da DOM que representa esse
componente.
Internamente o React vai garantir de que aquele “this
” dentro de seus métodos
de classe se refere à instância. Embora o JavaScript não vincula a instância
automaticamente quando você usa uma referência para o método risada
.
A linhaconsole.dir
no métodorisada
vai mostrar corretamente a instância do
componente porque aquele método foi chamado diretamente do método render
especificando o chamador explícitamente (this
). Você poderá ver o objeto
RisadaCruel
no console quando você executar o código abaixo:
Acontece que quando você usa o mesmo método em uma situação em que esse método
não vai ser executado instantaneamente como um event handler da vida, “quem” irá
chamar aquele método não vai mais ser explícito e a linha do console.dir
não
vai reportar a instância do componente como antes.
No codigo acima o React invoca o método evento
quando você clica na string,
mas não lhe dará acesso à instância do componente dentro dela fazendo com que
você receba um undefined
na sua cara quando você clicar na string. Esse é um
problema se seu método de classe precisa ter acesso a coisas como this.props
e
this.state
. Isso simplesmente não irá funcionar do jeito que você imagina.
Calma, existem varias solucoes para esse problema. Você pode abraçar o método em
uma função inline ou usar a chamada .bind
para forçar o método a lembrar quem
o chamou. Essas são soluções ok se seus componentes não são atualizados tão
frequentemente. Você pode também otimizar o método bind
do JavaScript chamando
ele lá no construtor da classe do seu componente ao invés de fazer isso no
método render
que é chamado constantemente.
Agora você pode estar se perguntando… Mas pera ai, porque eu faria isso? não
entendi nada! E eu lhe diria em seguida.. Sim, isso seria totalmente normal meu
caro aventureiro, vamos entender isso melhor!
Entendendo um pouquinho de otimização em React
Imagine a situação em que você esteja passando funções/eventos com a chamada
.bind
visando definir uma ação para os botões de cada item de uma lista que
você esteja criando lá dentro do metodo render
. Acontece que em JavaScript,
quando o método bind
recebe o seu primeiro argumento this
em sua chamada,
isso é justamente para definir explicitamente quem chamou aquela função, ou
seja, o contexto no qual você quer que essa função seja executada. Assim o
método bind
retorna uma função novinha em folha que retorna uma chamada a sua
função original forçando esse this
, ou seja, fazendo com que a função original
reconheça quem for que seja o argumento this
como quem a chamou,
consequentemente quem é o seu contexto de chamada. Como seria essa função na
vida real?
2 exemplos simplificados da implementação da função bind
:
function
(algumaFuncao, algumThis) {
return function() {
return algumaFuncao.apply( algumThis, arguments );
};
}
objetoLegal = {};
algumaFuncao = function() {};
funcaoLegal = bind(algumaFuncao, objetoLegal);
// funcaoLegal agiria como se fosse: objetoLegal.algumaFuncao();
Function.prototype.
= function(algumThis) {
return function() {
return this.apply(algumThis, arguments);
}
};
objetoFamoso = {};
algumaFuncao = function() {};
funcaoFamosa = this.algumaFuncao.bind(objetoFamoso);
// funcaoFamosa agiria como se fosse: objetoFamoso.algumaFuncao();
- A implementação oficial do método
bind
é muito mais sofisticada do que isso. - Existem dois métodos para definição explícita de contexto em JavaScript, o
métodocall
e oapply
que vieram antes do métodobind
com o advento do
ES6. - A diferença entre o método
call
e oapply
é que oapply
lhe deixa chamar a
função com o parâmetroarguments
sendo um array tipo
algumaFuncao.apply(algumThis, arrayDeArgumentos)
. Já ocall
você terá que
chamar com os parâmetros explicitamente separados por vírgulas tipo
algumaFuncao.call(algumThis, arg1, arg2, ...)
. Uma mnemónica útil para
entender a diferença entre esses dois métodos é “A para array e C para
comma.”.** Créditos por essa explicação para
flatline.** - Use o método
bind
quando quiser que essa função seja chamada posteriormente
com um determinado contexto. Usecall
ouapply
quando você quiser invocar a
função imediatamente modificando ou forçando o contexto.
Uhum, depois disso tudo que você escreveu ainda nao sei o que que isso tem haver
com otimização!
Tyler The Sloth by allheartsgoboom
Acontece que em JavaScript objetos complexos como as funções são instâncias
diferentes e possuem identidades diferentes, sendo assim a verificação de
igualdade superficial que o React faz entre os componentes sempre produz
resultado falso, ou seja, sempre resulta em diferença! 😨
Isso faz com que o React renderize novamente esses componentes que receberam
essa função novinha e única (em termos de referência) que foi criada toda vez
que o método bind
foi chamado naquele evento dentro do método render
do
componente.
A mesma coisa acontece se ao invés de utilizar uma função você esteja passando
um array
para cada item dessa lista no render
, porque em JavaScript, o
literal array
é a mesma coisa que chamar new Array()
que cria uma nova
instância (única em termos de referência) de um array
. Isso destrói (muito
trágico) completamente toda otimização de renderização pura do React!
Outra situação onde uma função novinha é criada no render
causando essa coisa
toda que eu falei:
class
extends PureComponent {
render() {
return ;
}
}
Isso mesmo, essa arrow
function
é de fato uma função que não foge a regra, nesse caso vai ser também uma
entidade única para cada Input
como mostra o caso acima. É interessante saber
dessas coisas né? já fiz “errado” muitas vezes! mas calma lá em, esse errado na
verdade é um pseudo errado porque cada caso é um caso e na maioria das vezes a
performance não vai ser um ponto crítico para você. A não ser, é claro, que seja
o caso de uma lista gigante, animações, etc. Enfim, se seu componente atualiza
constantemente.
Voltando ao exemplo do nosso componente RisadaCruel
Uma melhor solução para esse método atualmente é habilitar o recurso de
class-fields (campos de classe) do ECMAScript (no qual esta em stage-3) através
do Babel e simplesmente utilizar uma arrow function para os handlers:
class RisadaCruel extends React.Component {
render() {
return (
Hello World
);
}
}
Nice! mas pera ai, habilitar o recurso de class-fields em stage-3**? Como é
que é esse stage aí?**
No TC39, o comitê que desenvolve o JavaScript no qual seus membros são empresas
(entre outros camaradas como também todos os principais fornecedores de
navegadores) e cada proposta de um recurso para a linguagem passa por vários
estágios de maturidade, começando pelo estágio 0. O stage-3 ou estágio 3 que foi
mencionado anteriormente, significa que a proposta de tal recurso está
praticamente finalizada e agora precisa de feedback de implementações e usuários
para progredir ainda mais. Mais detalhes do TC e dos outros estágios
aqui.
fecharPost.bind(this);
É isso aí pessoal! espero ter desmistificado um pouco do limiar entre o que é
realmente JavaScript do que é realmente coisa do React e também como o React se
comporta com o uso “as vezes” inadequado do JavaScript em uma aplicação React. É
sabendo um pouco mais da ferramenta que utilizamos que podemos escrever
aplicações ainda melhores e mais performáticas.
Fim!!!
E valeu por ter chegado até aqui! 🤓
Está interessado ou já é explorador do mundo React? dê uma olhada no meu outro
post sobre tutoriais fantásticos e onde habitam!