React do zero: componentização, propriedades e estado

Entenda os principais conceitos do React — Parte 1

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:

Exemplo de interface separada por componentes (Fonte: http://nitrajka.com/)

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?

spinner_vs_shimmer
A seguir:

Implementando Shimmer Effect no React Native

Implementando Shimmer Effect no React Native