Componentes Funcionais e Componentes de Classe

Componentes Funcionais e Componentes de Classe

No que diz a respeito sobre componentes em ReactJS, você já viu que eles representam pequenos blocos de layout que quando juntos formam um componente maior se tornando containers ou pages (páginas).

Mas você sabia que é possível passar dados e informações para dentro dos nossos componentes?

Isso faz com que eles consigam processar tais artefatos, de modo a realizar renderizações condicionais de acordo com os dados e informações fornecidas por outro componente pai.

Até então, nós já sabemos como criar componentes estáticos dentro das nossas aplicação e também como declarar variáveis e funções dentro deles.

Nesta lição, vamos aprender a tornar os nossos componentes operacionais 😄

Criando o seu projeto

Antes de criar tornar nossos componentes operacionais, vamos começar criando um novo projeto em ReactJS. No meu caso eu nomeei ele como componentes-operacionais:

npx create-react-app componentes-operacionais

Não se esqueça de realizar aquela limpeza de código, e também fazer aquela organização na estrutura de pastas (Organização por Funcionalidade).

Feito isso, vamos criar o nosso primeiro componente chamado de Header:

index.jsx:

import './header.css';

const Header = () => {
 return (
 <header>
 <h1>Olá Micilini Roll!</h1>
 </header>
 );
}

export default Header;

header.css:

header{
 background-color: #34495e;
 width:100%;
 height: 80px;
 display: flex;
 justify-content: center;
 align-items: center;
}

header h1{
 color:white;
 font-size: 18px;
}

Não se esqueça de chamar o componente que acabamos de criar para dentro do App.js:

import Header from './components/Header';

export default function App() {
 return(
 <>
 <Header />
 </>
 )
}

Legal, e como resultado final nós teremos a seguinte tela abaixo:

Feito isso, agora nós precisamos aprender um pouco mais sobre as props 🙂

O que são Props em ReactJS?

No contexto do ReactJS, às props são uma forma abreviada de dizer "properties" (propriedades), e que representam uma forma de passar dados de um componente pai para um componente filho em forma de atributos do JSX.

Você se lembra que no HTML5 você pode definir alguns atributos dentro de inputs e outros elementos, como:

  • value=""
  • placeholder=""
  • atributo-customizado=""
  • width=""
  • height=""

E entre os inúmeros outros tipos de atributos que o HTML5 nos disponibiliza?

Então, se eu te disser que se você tentar passar um determinado atributo dentro da tag que representa o seu componente, tal atributo se torna uma propriedade (props)? Vejamos como essa mágica acontece 🪄

Vamos pegar como exemplo o nosso componente Header que acabamos de criar no tópico anterior. No arquivo App.js tal componente está sendo declarado da seguinte forma:

<Header />

Supondo que queremos passar um determinado nome para dentro do componente, de modo que o próprio componente diga "Olá Nome" em vez de "Olá Micilini Roll".

Para isso, você precisa declarar um novo atributo chamado nome dentro da tag <Header />, e passar como valor o nome que você deseja, por exemplo:

<Header nome="Gabriel Solano" />

Ao rodar o seu projeto (npm start), inicialmente você não vai ver nenhuma alteração, e o seu componente ainda vai estar mostrando a mesma mensagem:

Isso aconteceu pois apesar de estarmos passando uma nova propriedade para dentro do nosso componente, da forma atual como o nosso componente está escrito, ele não está tratando esse valor, e é por esse motivo que o nome "Micilini Roll" ainda continua aparecendo.

Para resolver isso, você precisa abrir o arquivo Header > index.jsx e declarar uma nova variável temporária - onde nós costumamos chamá-la de props -.

import './header.css';

const Header = (props) => {
 return (
 <header>
 <h1>Olá Micilini Roll!</h1>
 </header>
 );
}

export default Header;

É dentro da variável props que o ReactJS (por de baixo dos panos) vai pegar todos os atributos que declaramos dentro da tag do nosso componente (<Header />) transformando-os em objetos que existem dentro da variável que declaramos (props).

Dessa forma, nós conseguimos abrir um script dentro do return do nosso componente, de modo a informar a chave do objeto que contém o mesmo nome da propriedade que nós passamos:

import './header.css';

const Header = (props) => {
 return (
 <header>
 <h1>Olá {props.nome}</h1>
 </header>
 );
}

export default Header;

Note que selecionamos a variável props que contém toda a estrutura de chave e valor dos atributos que são passados para o componente, onde pegamos a chave nome, que contém o valor "Gabriel Solano".

Veja como ficou o resultado final:

Este tipo de componente que acabamos de criar,  possuem um nome e são conhecidos como Componentes Funcionais (functional components).

Componentes Funcionais são uma forma de definir componentes em ReactJS utilizando funções JavaScript, onde geralmente não possuem um ciclo de vida atrelado a eles, isso faz com que eles sejam limitados a ponto de receber dados e informações de maneira bruta e nada além disso.

Ou seja, você simplesmente passa uma determinada informação que ele precisa exibir e pronto, nada mais pode ser feito, mesmo que o nome da pessoa mude randomicamente dentro do componente pai.

"Mude randomicamente?"

Veremos como isso funciona quando falarmos sobre componentes de estado 😝

Passando mais de um atributo para dentro de um componente

Também é possível passar mais de um único atributo para o mesmo componente.

Pegando como exemplo o componente Header, vamos supor que queremos passar mais dois atributos para dentro dele, como é o caso dos atributos idade e rank, para isso basta declará-los normalmente como fizemos anteriormente:

<Header nome="Gabriel Solano" idade="28" rank="88.9" />

Já dentro do componente, você só precisa acessar as keys que existem dentro da variável props:

import './header.css';

const Header = (props) => {
 return (
 <header>
 <h1>Olá {props.nome} ({props.idade}) - Rank: {props.rank}</h1>
 </header>
 );
}

export default Header;

E como resultado:

É importante ressaltar que não existe um determinado limite de quantas propriedades um componente pode receber.

"O que será que acontece, se dentro do componente eu tentar acessar uma chave que não existe?"

Para responder essa pergunta, vamos analisar o código abaixo:

<Header nome="Gabriel Solano" />

Note que estamos passando para dentro do componente Header somente um único atributo, chamado de nome.

Já dentro do componente, vamos supor que estamos lendo uma propriedade chamada idade, que inicialmente não foi declarada nos atributos do componente:

import './header.css';

const Header = (props) => {
 return (
 <header>
 <h1>Olá {props.nome} ({props.idade})</h1>
 </header>
 );
}

export default Header;

Como resultado final, o ReactJS não vai apontar nenhum tipo de erro e seu código será executado normalmente. Mas o valor esperado vai ficar em branco:

Passando variáveis para dentro de um componente

Você também pode passar algumas variáveis que estão declaradas no escopo do componente pai para dentro do componente filho.

Para isso, você deve passar essas informações por meio das chaves {} da seguinte forma:

App.js:

import Header from './components/Header';

export default function App() {
 const nome = "Gabriel Solano";
 return (
 <>
 <Header nome={nome} />
 </>
 );
}

Observe que estamos passando o atributo nome informando o valor que existe dentro da constante nome ("Gabriel Solano").

Já dentro do componente Header, você não precisa modificar nada, pois a variável props já reconhece a key nome.

"É possível passar mais de um atributo seguindo este estilo?"

Sim, observe que no código abaixo estamos declarando três variáveis e passando cada uma delas para dentro do componente.

export default function App() {
 const nome = "Gabriel Solano";
 const idade = 28;
 const rank = 88.9;
 return (
 <>
 <Header nome={nome} idade={idade} rank={rank} />
 </>
 );
}

Passando funções para dentro de um componente

Em alguns casos, você vai precisar passar funções declaradas dentro de um componente pai para um componente filho, mas que serão executadas posteriormente dentro de um componente filho.

Para testar esse tipo de funcionalidade, vamos abrir o nosso App.js e declarar uma nova função chamada getNome:

export default function App() {
 const getNome = () => {
 return "Gabriel Solano";// Função que retorna o nome
 }; 

 return (
 <>
 <Header nome={getNome} />
 </>
 );
}

Note que estamos passando a função para dentro da propriedade nome.

Já dentro do componente (Header), se você só executar {props.nome} nada vai acontecer, pois nós passamos uma função e não um valor estático, nesse caso, precisamos executar nossa função para que ela retorne o nome da pessoa.

Observe como isso é feito:

const Header = (props) => {
 return (
 <header>
 <h1>Olá {props.nome()}</h1>
 </header>
 );
}

export default Header;

Caso desejar, você pode criar uma variável dentro do Header responsável por executar essa função:

const Header = (props) => {
 const nome = props.getNome();
 return (
 <header>
 <h1>Olá {nome}</h1>
 </header>
 );
}

export default Header;

E como resultado final, nós temos:

Componentes dentro de componentes

Como você viu em lições anteriores, no ReactJS é possível termos componentes aninhados (componentes que são chamados dentro de outros componentes).

E da mesma forma, também podemos passar um atributo do componente pai para o componente filho, que por sua vez passará para outro componente filho.

Para testar essa funcionalidade, você vai precisar criar um outro componente que existirá dentro do Header.

Neste caso, vamos criar um novo componente chamado de Rank:

index.jsx:

import './rank.css';

const Rank = (props) => {
 return (
 <>
 <span> Rank: {props.rank} </span>
 </>
 );
}

export default Rank;

rank.css:

span{
 color: #f1c40f;
 font-size: 20px;
 font-weight: bold;
}

Repare que nós já criamos o nosso componente Rank configurado para receber as propriedades (props).

Voltando agora para o nosso App.js, precisamos passar o atributo rank para dentro do componente Header, que por sua vez, vai repassar tal atributo para o componente Rank.

App.js:

<Header nome="Gabriel Solano" rank="88.9" />

Header > index.jsx:

import './header.css';
import Rank from '../Rank';

const Header = (props) => {
 return (
 <header>
 <h1>Olá {props.nome} - <Rank rank={props.rank} /></h1>
 </header>
 );
}

export default Header;

Como resultado final, nós temos:

É importante ressaltar que você também pode passar variáveis e funções para dentro de componentes aninhados, assim como você aprendeu em tópicos anteriores 😄

Componentes do tipo Classe

Até o momento você aprendeu a trabalhar com componentes funcionais (functional components), mas também existe um outro tipo de componente chamado de componentes de classes.

Componentes de classes são uma forma mais antiga de criar componentes antes da introdução dos componentes funcionais e dos componentes de estado (Hooks).

Eles são definidos como classes do JavaScript que estendem a classe React.Component e possuem um método render() que retorna o conteúdo a ser renderizado.

Para você poder entender como eles funcionam, vamos criar um outro componente chamado de Footer:

Dentro de Footer > index.jsx, vamos criar o nosso primeiro componente do tipo classe seguindo as orientações passadas acima:

import React, { Component } from 'react';
import './footer.css';

class Footer extends Component {
 render() {
 return (
 <footer>
 <p>© 2024 - Todos os Direitos Reservados!</p>
 </footer>
 );
 }
}

export default Footer;

footer.css:

footer {
 position: fixed;
 bottom: 0;
 left: 0;
 width: 100%;
 background-color: black;
 height: 40px;
 display: flex;
 justify-content: center;
 align-items: center;
}

footer p {
 color: white;
 font-size: 1.2rem;
}

Para que nosso Footer entre em ação, precisamos declará-lo dentro do App.js:

import Footer from './components/Footer';

export default function App() {
 return (
 <>
 <Footer />
 </>
 );
}

O resultado final será:

A princípio parece um componente bem comum, a única diferença é que ele foi declarado dentro de uma classe e não dentro de uma função.

"Ok, mas qual é a diferença entre ter um componente declarado dentro de uma classe?".

Um dos ganhos atrelados a este tipo de componente, é que agora nós podemos controlar o seu ciclo de vida, funções do Javascript, e ainda ter a possibilidade de controlar os estados dos componentes 🤩

Passando atributos para os componentes do tipo classe

Assim como fizemos nos componentes funcionais, também podemos passar parâmetros para os componentes do tipo classe.

Vamos pegar como exemplo o nosso Footer que acabamos de criar. Primeiro devemos declarar um novo atributo dentro de App.js:

<Footer ano="2024" />

E para recebê-lo dentro do Footer, nós precisamos fazer o uso do comando this

class Footer extends Component {
 render() {
 return (
 <footer>
 <p>© {this.props.ano} - Todos os Direitos Reservados!</p>
 </footer>
 );
 }
}

Mas espere aí... onde está a declaração da variável props?

No ReactJS, quando você cria um componente de classe, as props são acessíveis através do objeto this.props dentro dos métodos da classe. 

Isso significa dizer que por de baixo dos panos (dentro do método construtor da classe), o ReactJS se encarrega de criar essa variável para nós acessarmos depois. Diferente do que acontece nos componentes funcionais, onde precisamos declarar o nome da variável que vai receber os atributos.

Observe como é construído o método construtor da classe:

class Footer extends Component {
 constructor(props) {
 super(props);
 }

 render() {
 return (
 <footer>
 <p>© {this.props.ano} - Todos os Direitos Reservados!</p>
 </footer>
 );
 }
}

Se você deseja alterar o nome da variável que recebe as propriedades, você vai precisar declarar um método construtor dentro do componente Footer da seguinte forma:

class Footer extends Component {
 constructor(micilini) {
 super(micilini);
 this.micilini = micilini.ano; // Definindo micilini com o valor de ano
 }

 render() {
 return (
 <footer>
 <p>© {this.micilini} - Todos os Direitos Reservados!</p>
 </footer>
 );
 }
}

Passando mais de um atributo para dentro de um componente de classe

Também é possível passar mais de um atributo para dentro de um componente de classe, para isso basta declarar seus atributos dentro da tag que representa o seu componente:

<Footer ano="2024" site="https://micilini.com" />

Já dentro do seu componente, é só acessá-los por meio do this.props:

class Footer extends Component {
 render() {
 return (
 <footer>
 <p>© {this.props.ano} - Todos os Direitos Reservados! {this.props.site}</p>
 </footer>
 );
 }
}

Veja como ficou o resultado final:

Passando variáveis para dentro de um componente de classe

Também é possível passar variáveis diretamente para dentro de um componente de classe, veja como isso é feito:

App.js:

export default function App() {
 const ano = 2024;
 const site = 'https://micilini.com';
 return (
 <>
 <Footer ano={ano} site={site} />
 </>
 );
}

Footer > index.jsx:

class Footer extends Component {
 render() {
 return (
 <footer>
 <p>© {this.props.ano} - Todos os Direitos Reservados! {this.props.site}</p>
 </footer>
 );
 }
}

Resultado final:

Passando função para dentro de um componente de classe

Também é possível passar funções a serem executadas dentro de um componente de classe, veja como isso é feito.

App.js:

export default function App() {
 const getAno = () => {
 return 2024;
 };
 return (
 <>
 <Footer ano={getAno} />
 </>
 );
}

Já dentro do Footer, é só executar a função que acabamos de receber usando o nome do atributo retornado (ano).

class Footer extends Component {
 render() {
 return (
 <footer>
 <p>© {this.props.ano()} - Todos os Direitos Reservados!</p>
 </footer>
 );
 }
}

Veja como ficou o resultado final:

Porque chamamos this.props.ano() em vez de this.props.ano.getAno()?

Fizemos isso pois quem recebeu a função de execução foi a variável ano, o que a torna a executora da função getAno(). É como se a função getAno() se tranformasse em ano().

Caso desejar, você pode executar essa função diretamente na propriedade, e fazer com que o componente já receba o dado estático:

export default function App() {
 const getAno = () => {
 return 2024;
 };
 return (
 <>
 <Header nome="Gabriel Solano" rank="88.9" />
 <Footer ano={getAno()} />
 </>
 );
}
class Footer extends Component {
 render() {
 return (
 <footer>
 <p>© {this.props.ano} - Todos os Direitos Reservados!</p>
 </footer>
 );
 }
}

O resultado final será o mesmo 🙂

Componentes de classe dentro de componentes de classe

Nos componentes do tipo classe, você também pode criar componentes aninhados.

Para testar essa funcionalidade, vamos criar um novo componente chamado de Site:

Site > index.jsx:

import React, { Component } from 'react';
import './site.css';

class Site extends Component {
 render() {
 return (
 <>
 <small>{this.props.site}</small>
 </>
 );
 }
}

export default Site;

site.css:

small{
 font-size: 14px;
 text-decoration: underline;
}

Após isso, vamos importar esse componente para dentro de Footer da seguinte forma:

import React, { Component } from 'react';
import './footer.css';
import Site from '../Site';

class Footer extends Component {
 render() {
 return (
 <footer>
 <p>© {this.props.ano} - Todos os Direitos Reservados! <Site site={this.props.site} /></p>
 </footer>
 );
 }
}

export default Footer;

Por fim, não se esqueça de passar o atributo site também no App.js:

export default function App() {
 return (
 <>
 <Footer ano="2024" site="https://micilini.com/" />
 </>
 );
}

Veja como ficou o resultado final:

Ciclo de vida em um componente do tipo classe

Você se lembra que em tópicos anteriores eu cheguei a mencionar que uma das vantagens de se criar um componente do tipo classe está relacionada com seu ciclo de vida?

Quando me refiro ao ciclo de vida de um componente, estou fazendo uma alusão aos métodos que são chamados após o carregamento daquele componente no DOM.

Observe o componente abaixo:

import React, { Component } from 'react';

class ComponenteExemplo extends Component {
 constructor(props) {
 super(props);
 this.state = {
 message: 'Componente está montado!',
 };
 console.log('Constructor');
 }

 componentDidMount() {
 console.log('ComponentDidMount');
 // Este método é chamado após o componente ser montado no DOM
 // É um bom lugar para fazer chamadas a APIs externas ou iniciar animações
 }

 componentDidUpdate(prevProps, prevState) {
 console.log('ComponentDidUpdate');
 // Este método é chamado após o componente ser atualizado
 // É um bom lugar para fazer operações de atualização de estado baseadas em mudanças de props ou estado
 }

 componentWillUnmount() {
 console.log('ComponentWillUnmount');
 // Este método é chamado imediatamente antes do componente ser desmontado e destruído
 // É um bom lugar para limpar recursos, como cancelar assinaturas ou remover eventos
 }

 render() {
 console.log('Render');
 return (
 <div>
 <h1>Ciclo de Vida do Componente</h1>
 <p>{this.state.message}</p>
 </div>
 );
 }
}

export default ComponenteExemplo;

Aqui, nós temos o ComponenteExemplo do tipo de classe, que conta com 4 métodos principais, que são responsáveis por controlar o seu ciclo de vida, são eles:

constructor: Este é o primeiro método chamado ao instanciar o componente. É usado para inicializar o estado e definir outras configurações iniciais.

componentDidMount: Este método é chamado depois que o componente foi montado no DOM. É um bom lugar para fazer chamadas a APIs externas ou para iniciar animações.

componentDidUpdate: Este método é chamado sempre que o componente é atualizado. É um bom lugar para fazer operações de atualização de estado baseadas em mudanças de props ou estado.

componentWillUnmount: Este método é chamado imediatamente antes do componente ser desmontado e destruído. É um bom lugar para limpar recursos, como cancelar assinaturas ou remover eventos.

No exemplo acima, cada método imprime uma mensagem no console para mostrar quando ele está sendo chamado. Isso pode te ajudar a entender a ordem em que os métodos do ciclo de vida são executados durante a vida útil do componente.

E como mencionado, cada um desses métodos são chamados em momentos específicos, o que os tornam ideal para realizar chamadas de APIs externas, iniciar animações, alterar props ou remover eventos.

Entendendo mais a fundo os ciclos dos componentes

Para entender mais a fundo o funcionamento do ciclo de vida dos componentes, vamos dar uma repaginada no nosso App.js para que ele fique dessa forma:

import React, { Component } from 'react';

class App extends Component{
 render(){
 return(
 <div>
 <h1>Olha a Hora</h1>
 </div>
 );
 }
}

export default App;

E sim, você também pode converter o seu App.js para um componente do tipo classe 😅

Em seguida vamos criar um cronômetro que vai contabilizar as horas a partir do momento que a nossa aplicação é montada:

import React, { Component } from 'react';

class App extends Component{

 constructor(props){
		//O construtor da classe é a primeiro método a ser executado quando o componente é chamado.
 super(props);
 this.state = {
 hora: '00:00:00'
 };
 }

 componentDidMount(){
	 //Este método é chamado quando o componente é montado no DOM. Ele é chamado apenas uma única vez.
		
 setInterval(() => {
 this.setState({ hora: new Date().toLocaleTimeString() });
 }, 1000);

 }

 componentDidUpdate(){
 //Este método é sempre chamado quando um componente é atualizado.
 console.log('Atualizou!');
 }

 shouldComponentUpdate(){
 //Este método é sempre chamado antes do componente ser atualizado. Aqui podemos retornar true ou false.
 return true;
 }

 render(){
 return(
 <div>
 <h1>Olha a Hora: {this.state.hora}</h1>
 </div>
 );
 }
}

export default App;

No exemplo acima os principais ciclos de vida que utilizamos é o componentDidUpdate e o componentDidMount

Ao executar o projeto um cronometro será executado e continuará sendo chamado a cada milisegundo.

Etapas do ciclo de vida de um componente

Vamos detalhar agora o funcionamento de cada uma das etapas abaixo:

  • Inicialização
  • Montagem
  • Atualização
  • Desmontagem

Inicialização

Nesta fase, o componente em ReactJS se prepara para sua inicialização, configurando os estados iniciais e props padrões se houverem.

constructor() é o construtor da classe, aqui fazemos todas as preparações antes da montagem do nosso componente no DOM.

Montagem

Depois de preparar com todas as necessidades básicas, estado e props, o nosso componente está pronto para ser montado no DOM do navegador.

componentWillMount() é executado quando o componente estiver prestes a ser montado no DOM da página. Assim, após esse método ser executado o componente irá criar o nó no navegador. Todas as coisas que você deseja fazer antes do componente ser montado, devem ser definidas aqui.

componentDidMount() este é o método que é executado depois que o componente foi montado no DOM.

Atualização

Esta fase começa quando o componente já nasceu no navegador e cresce recebendo novas atualizações. O componente pode ser atualizado de duas maneiras, através do envio de novas props ou a atualização do seu estado.

componentDidUpdate() é chamado imediatamente após a atualização.

componentWillUpdate() é executado somente quando shouldComponentUpdate retornar true.

Desmontagem

Nesta fase, o componente não é mais necessário e será desmontado do DOM. O método que é chamado de maneira automática nesta fase é o componentWillUnmount().

Chamando funções que existem dentro de componentes de classe

Vamos supor que você queira chamar uma função que existe dentro de um componente de classe, como você faria isso?

Imagine que nós temos um componente chamado Computador, e que dentro dele temos um método chamado ligar, que é executado sempre quando o usuário clica no botão existente dentro do nosso componente:

import React, { Component } from 'react';

class Computador extends Component{

 constructor(props){
 super(props);
 this.state = {
 nomeDaMaquina: props.nome//Estamos recebendo o nome da máquina via props
 }

 this.ligar = this.ligar.bind(this);//Precisamos fazer o bind de todas os métodos que o render irá chamar
 }

 ligar(nome){
 this.setState({nomeDaMaquina: nome});
 }

 render(){
 return(
 <div>
 <h2>Sua Máquina é: {this.state.nomeDaMaquina}</h2>
 <button onClick={() => this.entrar('Intel Xeon 15th')}>Ligar</button>
 <button onClick={ () => this.setState({nomeDaMaquina: '(Desligada...)'}) }>Desligar</button>
 </div>
 );
 }
}

export default Computador;

Observe que dentro da função onClick, nos tanto chamamos o método entrar, quanto também usamos o setState diretamente dentro da função anônima.

Observe também que todos os métodos que formos chamar dentro do render, precisam estar "bindados" dentro do constructor, para que eles possam ser chamados pelo this.

De resto, todas as funcionalidades vistas anteriormente são Javascript puro, e podem ser aprendidas por meio dessa jornada.

Entendendo um pouco mais sobre o setState e o PrevState

Quando trabalhamos com componentes de classe, nós temos acesso aos comandos setState e prevState, que por sua vez, estão interligados com códigos JSX, de modo que a cada alteração que acontece no valor de uma de nossas variáveis, o conteúdo HTML que aparece para o usuário é atualizado em TEMPO REAL.

Vamos começar aprendendo sobre o setState, que é uma função fundamental para atualizar o estado de um componente e re-renderizá-lo quando necessário.

Quando você chama setState em um componente React, você está instruindo o React a atualizar o estado daquele componente em tempo real, por exemplo:

this.setState({ 
 // novo estado aqui 
});

O argumento que você passa para setState é um objeto que representa o novo estado que você deseja definir.

Desse modo, o ReactJS fundirá esse novo estado com o estado anterior e re-renderizará o componente com o estado atualizado. 

Vejamos um exemplo onde a variável nome está setada como fulano, e que também esta conectada no JSX. E assim que o usuário clicar no botão o valor dessa variável será alterado para micilini, onde o resultado será mostrado em tempo real sem a necessidade de atualizar a página ou fazer integrações manuais (como é o caso de listeners):

import React, { Component } from 'react';

class BoasVindas extends Component {
 constructor(props) {
 super(props);
 this.state = {
 name: 'Fulano'
 };
 }

 handleUpdateName = () => {
 this.setState({ name: 'Micilini' });
 }

 render() {
 return (
 <div>
 <p>Olá {this.state.name}</p>
 <button onClick={this.handleUpdateName}>Atualizar Nome</button>
 </div>
 );
 }
}

export default BoasVindas;

Veja como ficou o resultado final:

Isso é o poder do ReactJS em ação. Se fossemos fazer isso com Javascript puro é bem provavel que precisariamos usar uma biblioteca capaz de atualizar o valor do elemento <p> em tempo real.

Com o ReactJS nós não precisamos nos preocupar com ações manuais, pois os componentes de classe já fazem isso por de baixo dos panos.

Além do setState nós temos tambem o prevState que nada mas é uma função de callback opcional que você pode passar para setState.

Ele permite que você acesse o estado anterior antes da atualização, por exemplo:

this.setState((prevState) => {
 return { count: prevState.count + 1 };
});

Isso é útil quando você precisa calcular o próximo estado com base no estado anterior, especialmente em casos onde várias chamadas para setState podem ser enfileiradas e executadas de forma assíncrona.

Em relação aos componentes de classe, eles têm a capacidade de gerenciar o estado interno e os ciclos de vida dos componentes. 

Sendo assim, eles estendem a classe Component do ReactJS e usam o estado e os métodos de ciclo de vida como render(), componentDidMount(), componentDidUpdate(), etc para gerenciar esses ciclos.

Para vermos o funcionamento do prevState vamos criar um outro componente chamado de BoasVindasContador que ao clicar no botão para atualizar o nome, ele adicione um número ao final do nome atual.

Por exemplo, se o nome atual for "Micilini", ao clicar no botão, o nome se tornará "Micilini - 1", e assim por diante.

import React, { Component } from 'react';

class BoasVindasContador extends Component {
 constructor(props) {
 super(props);
 this.state = {
 name: 'Micilini',
 counter: 0
 };
 }

 handleUpdateName = () => {
 this.setState((prevState) => ({
 counter: prevState.counter + 1
 }));
 }

 render() {
 return (
 <div>
 <p>Olá {this.state.name} - {this.state.counter}</p>
 <button onClick={this.handleUpdateName}>Atualizar Nome</button>
 </div>
 );
 }
}

export default BoasVindasContador;

Veja como ficou o resultado final:

Observação: Esse tipo de mudança em tempo real no código HTML não é possível por meio dos componentes funcionais (possível até é, mas é necessário o uso dos Hooks, o que os tornam componentes de estado e não mais funcionais).

Componentes Funcionais VS Componentes de Classe

Para recapitular tudo o que nós aprendemos nos tópicos anteriores, vamos fazer um pequeno comparativo entre eles.

Sintaxes Diferentes: A sintaxe para criar um componente funcional é uma simples função em JavaScript que retorna JSX, enquanto um componente de classe é uma classe em JavaScript que estende de React.Component possuindo um método render() que por sua vez também retorna um JSX.

Controle de Estado: Componentes funcionais não têm estado interno próprio (até a introdução dos hooks). Eles geralmente são usados para renderização de UI pura e são conhecidos como "componentes sem estado". Componentes de classe, por outro lado, podem ter estado interno definido por this.state e podem ser atualizados usando this.setState().

Ciclo de Vida: Como vimos anteriormente os componentes de classe possuem alguns métodos que controlam o ciclo de vida da sua aplicação. Já os componentes funcionais não possuem isso (até a introdução dos hooks).

Legibilidade e Concisão: Componentes funcionais tendem a ser mais concisos e legíveis do que componentes de classe, especialmente para componentes simples que não requerem estado ou métodos de ciclo de vida. Isso pode tornar o código mais fácil de se entender e também de se manter.

Devo usar componentes funcionais ou componentes de classe?

Nenhum nem outro 😆

Atualmente (nas versões mais novas do ReactJS) já existem os famosos componentes de estado que fazem o uso de Hooks, o que permite que você use estados e outras características do ReactJS sem a necessidade de se escrever uma classe

Eles são funções que permitem que você "ligue" o estado e os ciclos de vida do ReactJS a componentes de função, permitindo que você use recursos do ReactJS em componentes de função, que anteriormente eram exclusivos de componentes de classe.

"Mas se é assim, por que você nos ensinou isso a criar e trabalhar com componentes de classe?".

Durante a sua jornada como desenvolvedor ReactJS, você poderá se deparar com projetos que ainda fazem o uso de componentes funcionais ou de componentes de classe, seja porque a empresa não se atualizou, ou porque o desenvolvedor responsável do projeto não quis se atualizar 😂

De qualquer forma é sempre bom entender como as coisas funcionavam em versão mais antigas do ReactJS, mesmo que a gente não vá fazer mais uso delas.

Arquivos da lição

Os arquivos dessa lição podem ser encontrados neste repositório do GitHub.

Conclusão

Nesta lição você viu como dar a vida aos seus componentes, e ainda aprendeu um pouco mais sobre o ciclo de vida dos mesmo 🥳

Na próxima lição aprenderemos a realizar uma Renderização Condicional em seus componentes!

Até a próxima!