Otimizando Componentes Do React Com Purecomponent
Performance e desempenho geralmente são grandes questões para levarmos em consideração durante a usabilidade de nossos webapp’s ou sites. Construir um webapp que seja performático e garantir uma boa usabilidade para os usuários do mesmo, nem sempre é uma tarefa simples de conquistar, muitas vezes vai exigir um bom tempo de dedicação para encontrar momentos de gargalos e um pouco mais ainda para pensar/implementar uma solução.
Para exemplo do post iremos abordar a biblioteca React. Como podemos tornar nossos componentes mais performáticos?
Criando um componente normal
A classe mais comum para ser utilizada durante a criação de nossos componentes é a velha conhecida Component
, através dela podemos criar um component comum. Para entender o problema, vamos criar um simples aplicativo onde temos um array
de números e através de um botão será possível adicionar novos números para esse array
.
Criando o nosso container:
// App.js
import React, { Component, Fragment } from 'react'
import NComponent from '../components/NComponent'
class App extends Component {
state = {
numbers: []
}
render() {
const { numbers } = this.state
return (
<Fragment>
<h1>App Container</h1>
<button onClick={this.handleBtnClick} type="button">
Add new number
</button>
<NComponent numbers={numbers} />
</Fragment>
)
}
handleBtnClick = () =>
this.setState(({ numbers }) => {
const MAX = 100
const MIN = 1
numbers.push(parseInt(Math.random() * (MAX - MIN) + MIN))
return {
numbers
}
})
}
export default App
Agora vamos implementar o NComponent
que será um componente normal do React utilizando a classe Component
:
// NComponent.js
import React, { Component, Fragment } from 'react'
class NComponent extends Component {
render() {
const { numbers } = this.props
console.log("NComponent => I'm rendering...")
return (
<Fragment>
<h1>Normal component</h1>
<ul>
{numbers.map((number, index) => (
<li key={index}>{number}</li>
))}
</ul>
</Fragment>
)
}
}
export default NComponent
Por fim, para que tudo funcione, devemos ter o nosso famoso index.js
:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './containers/App'
const rootElement = document.getElementById('root')
ReactDOM.render(<App />, rootElement)
Que apenas irá renderizar nosso container App
.
Se tudo ocorreu conforme o planejado, devemos ter o seguinte aplicativo:
Se você reparou, dentro da função render
adicionamos um log para informar que aquele componente foi renderizado:
render() {
// ... códigos omitidos
console.log("NComponent => I'm rendering...");
// ... códigos omitidos
}
Ao carregar a página pela primeira vez, olhando em nosso console, temos a seguinte saída:
Tudo funcionando como o esperado, agora, se tentarmos adicionar um novo número no array
:
Tudo funcionou como o esperado:
- Conseguimos adicionar um novo número para o
array
. - O novo número foi renderizado pelo nosso
NComponent
. - O log foi mostrado pois a função
render
doNComponent
foi chamada.
Até então não vimos nada diferente, mas, onde podemos começar a otimizar nosso aplicativo?
Entenda a classe Component
Por padrão um componente do tipo Component
sempre irá se atualizar quando uma propriedade ou um estado sofrer modificações. Isso pode ser melhorado implementando o ciclo de vida shouldComponentUpdate
. Essa função deve devolver um valor booleano
para casos onde true
significa que deve ser atualizado e false
onde não devemos atualizá-lo.
shouldComponentUpdate() {
return false
}
Nesse exemplo, o componente não será atualizado em momento algum (apenas para exemplo). A função shouldComponentUpdate
recebe dois parâmetros: próximo valor de props
e próximo valor de state
, dessa maneira é possível realizar alguma atualização condicional.
shouldComponentUpdate(nextProps, nextState) {
// sua condição aqui...
}
Mas, será que devemos ficar nos preocupando com esse tipo de coisa? E se fosse possível utilizar algo inteligente o suficiente para saber quando deve ser atualizado? Pois é, isso é possível.
Criando um componente puro
Pensando justamente nesse problema, a galera do React á partir da versão 15.3 disponibilizou um novo tipo de componente: o PureComponent
. A diferença dele para o Component
está justamente na inteligência, vamos fazer um teste?
// PComponent.js
import React, { Fragment, PureComponent } from 'react'
class PComponent extends PureComponent {
render() {
const { numbers } = this.props
console.log("PComponent => I'm rendering...")
return (
<Fragment>
<h1>Pure component</h1>
<ul>
{numbers.map((number, index) => (
<li key={index}>{number}</li>
))}
</ul>
</Fragment>
)
}
}
export default PComponent
A implementação do PComponent
é semelhante do NComponent
com a única diferença em seu tipo, agora estamos usando o PureComponent
ao invés de Component
. Com o componente novo criado, já podemos utilizá-lo dentro de nosso App.js
:
// App.js
import React, { Component, Fragment } from 'react'
import NComponent from '../components/NComponent'
import PComponent from '../components/PComponent' // importe-o
class App extends Component {
state = {
numbers: []
}
render() {
const { numbers } = this.state
return (
<Fragment>
<h1>App Container</h1>
<button onClick={this.handleBtnClick} type="button">
Add new number
</button>
<NComponent numbers={numbers} />
<PComponent numbers={numbers} /> // renderize o novo componente
</Fragment>
)
}
handleBtnClick = () =>
this.setState(({ numbers }) => {
const MAX = 100
const MIN = 1
numbers.push(parseInt(Math.random() * (MAX - MIN) + MIN))
return {
numbers
}
})
}
export default App
Novamente, olhando o resultado temos:
E novamente em nosso console, temos a seguinte saída:
Nada de novo, certo? Então, vamos tentar adicionar um novo número para nosso array
:
Ué, ele atualizou apenas o NComponent
, porque isso aconteceu?
Entenda a classe PureComponent
Como já foi mencionado anteriormente, um componente do tipo PureComponent
possuí uma inteligência, ele apenas irá atualizar quando de fato as referências de suas props
ou state
forem atualizadas, isso é feito através da implementação da função shouldComponentUpdate
(também já mencionado). Em outras palavras o PureComponent
já vem com uma implementação para a shouldComponentUpdate
.
Mas atualizamos nosso array
, então ele deveria ser atualizado também, porque isso não ocorreu? Vamos olhar a função responsável por receber o click do botão e adicionar um novo número para o array
:
handleBtnClick = () =>
this.setState(({ numbers }) => {
const MAX = 100
const MIN = 1
numbers.push(parseInt(Math.random() * (MAX - MIN) + MIN))
return {
numbers
}
})
Basicamente estamos pegando o state
de number
já existente e adicionando um novo item para ele, em momento algum estamos criando um novo array
e assim modificando sua referência. Vamos modificar a função para sempre devolver um novo array
:
handleBtnClick = () =>
this.setState(({ numbers }) => {
const MAX = 100
const MIN = 1
const newNumber = parseInt(Math.random() * (MAX - MIN) + MIN)
return {
numbers: [].concat(numbers, newNumber)
}
})
Agora estamos sempre criando um novo array
e atualizando nosso estado com o mesmo, voltando para o aplicativo e testando novamente:
E voalá, temos o resultado esperado:
- Um novo número é adicionado para o
array
. - O novo número foi renderizado pelo
NComponent
ePComponent
. - O log de
NComponent
ePComponent
foram logados.
Conclusão
Nesse post expliquei um pouco sobre o PureComponent
, quando devemos utilizá-lo e como ele funciona. Dessa maneira conseguimos trazer um ganho de performance para nossos aplicativos, pois, com a inteligência dos componentes, muitas chamadas para a função render
serão evitadas e teremos um ganho de processamento (o mesmo será menos exigido).
Caso queira ver o exemplo em funcionamento:
Se preferir, pode visualizá-lo diretamente no sandbox.
E aí, você já conhecia o PureComponent
? Não deixe de comentar, se gostou do post fique á vontade para se inscrever na newsletter e receber novidades por email.
Até a próxima.