React do zero: componentização, propriedades e estado
Esse post é a primeira parte da série “React do zero”.
Se você chegou até o React sem utilizar nenhuma outra biblioteca componentizada como Vue, Angular ou Polymer, provavelmente deve ter sofrido ou até estar sofrendo para entender o que é um componente, por que separar o código em componentes, o que é estado, wtf é Redux, etc… Fica tranquilo, nesse post você vai entender tudo isso.
O que é React?
O React é uma biblioteca para criação de interface e representa a camada de “View” em um modelo de projeto. Essa camada de visualização é criada a partir de componentes que representam funcionalidades isoladas em um sistema. Desenvolvida e disponibilizada pelo Facebook, o React é uma lib de código aberto e é mantida por milhares de desenvolvedores ao redor do mundo que contribuem de forma voluntária ao projeto.
Componentização
A principal diferença do React e de outras bibliotecas baseadas em componentes diante de libs como o jQuery, Angular 1 ou Javascript puro está em sua habilidade de separar as funcionalidades do software em componentes, mas o que são componentes?
Componentes são conjuntos isolados de lógica (Javascript), visualização (JSX/HTML) e possível estilização (CSS).
Mas só isso? Calma, existem muitos benefícios além de manter o código organizado.
Por que componentes?
Imagine a timeline do Facebook (grande case que originou o React), você roda a barra de rolagem até conter 500 posts em tela e então adiciona um comentário ao post de nº 250, imagine o quão trabalhoso é para a DOM do seu navegador entender que um único elemento no meio de tantos foi atualizado e enviar essa informação em tempo-real para os outros usuários do Facebook, ou até ouvir a atualização de 500 posts em tela de forma organizada, imagine controlar tudo isso com jQuery ou Javascript puro, será que é possível?
Separando os posts em componentes, cada item controla suas próprias informações e assim quando uma dessas publicações sofre alteração, seja comentário ou atualização em tempo real, a única que ela enxerga é a si mesmo e não precisa varrer toda DOM procurando pelo item correto a se atualizar.
Utilizando o conceito de componente, vamos visualizar a conversão dessa interface abaixo em componentes:
Veja que tudo que é visível ao usuário final da aplicação é obrigatoriamente um componente e os dividimos de forma a encapsular a lógica e estilização do mesmo, assim evitando o compartilhamento desnecessário de código entre outros componentes.
A face de um componente
Ok… mas como um componente se parece no código? Podemos encapsular nossos componentes de várias formas no React mas a mais comum é utilizar classes que estão disponíveis a partir do ES6(ECMAScript 2015):
import React from 'react';
class Post extends React.Component {
...
}
Veja que preciso extender a classe Component
presente no pacote do React toda vez que criar um novo componente baseado em classe.
Agora com uma classe para nosso componente, precisamos indicar o retorno desse componente ao ser renderizado em tela, e podemos fazer isso com o método obrigatório render
que deve retornar um código JSX (HTML dentro do Javascript, sim):
// components/Post.js
import React from 'react';
class Post extends React.Component {
render() {
return <h1>Hello World</h1>;
}
}
Estamos indicando que ao chamar nosso componente de Post, iremos apenas exibir uma tag <h1>
em tela com o texto “Hello World”. Mas como podemos chamar esse componente?
Primeiramente precisamos indicar que queremos que esse componente seja acessível por outros, exportando-o. Basta adicionar o prefixo export default
antes da definição da classe:
export default class Post extends React.Component {
...
}
Agora, como tudo no React são componentes, em qualquer outra classe do nosso projeto podemos importar nosso componente e exibí-lo dentro do método render
em forma de tag.
// components/Lista.js
import React from 'react';
import Post from 'Post';
class Lista extends React.Component {
render() {
return (
<Post />
<Post />
<Post />
);
}
}
Propriedades
Assim como no HTML, podemos repassar propriedades nas notações dos nossos componentes e acessá-las de forma muito rápida. No exemplo acima, vamos repassar um título para cada post através do componente de Lista e exibí-lo em tela no Post:
// components/Lista.js
import React from 'react';
import Post from 'Post';
class Lista extends React.Component {
render() {
return (
<Post title="Aprendendo React" />
<Post title="A RocketSeat é massa!" />
<Post title="Não sei mais outro título" />
);
}
}
Agora em nosso componente de Post podemos acessar essa propriedade facilmente acessando a variável this.props
:
// components/Post.js
import React from 'react';
class Post extends React.Component {
render() {
return <h1>{this.props.title}</h1>;
}
}
As propriedades não se limitam à strings, você pode repassar números, objetos, vetores ou qualquer outro tipo primitivo de variável ao componente.
obs: Para passar variáveis e números é necessário o uso das chaves: <Post numeroDeLikes={14} />
e variáveis: <Post comentarios={comentarios} />
.
Estado
Diferente das propriedades, o estado não é repassado ao componente e sim configurado dentro dele. Pense no estado como as propriedades de nossa classe que devem ser armazenadas para renderizarmos o componente da forma correta.
Dessa forma em nosso componente precisamos de uma variável para armazenar essa lista de posts. Podemos definir nosso estado com uma variável state
logo após a definição da classe:
// components/Lista.js
import React from 'react';
import Post from 'Post';
class Lista extends React.Component {
state = {
posts: [
{ id: 1, title: 'Aprendendo React' },
{ id: 2, title: 'A RocketSeat é massa!' },
{ id: 3, title: 'Ainda não sei outro título' }
],
};
render() {
...
}
}
Agora com nosso state
inicial definido, precisamos atualizar nosso método render
para construir nossa “View” baseada em nosso estado. Podemos acessar o estado através do render
com a variável this.state
e agora vamos percorrer nossa variável de posts com o método map
do Javascript:
// components/Lista.js
class Lista extends React.Component {
state = { ... };
render() {
return (
{ this.state.posts.map(post =>
<Post key={post.id} title={post.title} />
)}
)
}
}
obs: Perceba que adicionei uma propriedade key
ao <Post>
, isso porque, em todo componente pai dentro de um map
, precisamos definir uma propriedade key
única para auxiliar o React na hora de renderizar (como um array).
Fluxo de renderização
Agora que entendemos como criar o estado e exibí-lo em tela, precisamos entender por que criar uma variável state
e não simplesmente uma variável na classe que contém essas informações.
O estado do componente não é uma simples variável, ele é gerenciado pelo React que determina a necessidade de cada componente ser renderizado novamente, ou seja, sempre que o nosso estado sofrer alguma alteração em uma informação utilizada dentro do método render
nosso componente é montado novamente com as novas informações, em tempo-real, nesse caso, se adicionarmos um novo post ao fim do vetor de posts do estado, ele irá disparar um novo render
exibindo-o em tela.
Alterando o estado
Apesar do estado ser uma variável em nossa classe acessada diretamente pelo this.state
, não podemos alterar o estado da mesma forma que alteramos uma variável comum, isso porque o estado é imutável, ou seja, ele nunca deve ser alterado e sempre deve ser sobreposto, dessa forma, se precisarmos adicionar um post ao fim da lista, precisamos na verdade redefinir essa variável com o novo post ao fim.
Para facilitar esse processo, podemos utilizar a função setState
em qualquer parte do componente e dessa forma repassamos apenas as variáveis que iremos atualizar no estado, deixando de lado qualquer outra informação que não iremos modificar e o React irá apenas copiar elas para o novo estado.
No exemplo abaixo, irei adicionar um novo post ao fim do vetor de posts do estado após 2 segundos utilizando o método constructor
presente em toda classe do Javascript.
// components/Lista.js
class Lista extends React.Component {
state = {
posts: [
{ id: 1, title: 'Aprendendo React' },
{ id: 2, title: 'A RocketSeat é massa!' },
{ id: 3, title: 'Ainda não sei outro título' }
],
};
constructor(props) {
super(props);
setTimeout(() => {
this.setState({
posts: [
...this.state.posts,
{ id: 4, title: 'Novo post no fim da lista' }
]
});
}, 2000);
}
render() {
...
}
}
Veja que em nenhum momento eu altero a variável posts e sim crio uma nova variável copiando toda lista de posts e repassando um novo item ao fim. Se você não entendeu alguma sintaxe que utilizei acima, veja esse post sobre ES6 que escrevi.
obs: toda vez que definido, o constructor precisa repassar a variável props
recebida para a classe Component
herdada, por isso utilizo o super(props)
.
Concluindo
Nesse post você viu por que o React separa seu código em componentes e pôde aprender sobre propriedades e estado. Na próxima parte dessa série vamos falar sobre ciclo de vida dos componentes, arquitetura flux e ir mais a fundo nos conceitos desse post, até lá deixo um vídeo que disponibilizei no YouTube da RocketSeat com um exemplo do que vimos nesse post em React:
Quer ver a segunda parte dessa série?