Componentes com Typescript
Olá leitor, seja bem vindo a segunda lição da jornada do desenvolvedor ReactTS (React + Typescrpt 😆)!
Na lição anterior, você aprendeu a iniciar um novo projeto em React com o Typescript, e isso se deu pelo uso da flag --template typescript
, o que faz com que a nossa aplicação faça o uso do superset Typescript.
Além disso, você viu que a organização das pastas do projeto não mudou muita coisa:
As únicas diferenças são os usos das novas extenções .ts
e .tsx
, além é claro, desses arquivos estarem todos tipados, pois o Typescript nos obriga a seguir por esse caminho 🤓
A partir de agora, nós iremos nos aprofundar um pouco mais em alguns conceitos que já vimos anteriormente na jornada do ReactJS, só que dessa vez, iremos utilizar o Typescript em vez do Javascript!
Vamos nessa? 😄
Criando a estrutura da nossa aplicação inicial (setup limpo)
Como já estamos careca de saber (e também fazer), tudo começa com a criação de um novo projeto em ReactTS, e para isso, nada mais justo que copiar e colar aquele comando padrão na pasta raíz aonde você vai armazenar todos os projetos:
npx create-react-app 2-setup-limpo --template typescript
Observação: Na aula anterior, eu criei uma pasta na minha área de trabalho (desktop) chamada de jornadaReactTS, logo, eu executei o comando acima dentro do meu terminal (prompt de comando), que por sua vez, estava setado dentro da minha pasta (jornadaReactTS).
O comando acima, vai criar um novo projeto em React chamado de 2-setup-limpo
usando o superset Typescript.
Após isso, não se esqueça de fazer aquela limpeza do código, e também aquela organizada nas pastas do src (organização por funcionalidade) 😁
Por fim, você terá uma estrutura limpa similar a esta:
Este projeto pode ser baixado neste repositório do GitHub, e você poderá usá-lo sempre que quiser 😉
Lembrando que você pode executar o seu projeto usando o comando npm start
ou npm run start
, de dentro da pasta raiz do projeto.
Criando seu primeiro componente
O processo de criação de um novo componente no ReactTS, não se difere muito daquilo que já estamos acostumados a fazer na versão do Javascript (ReactJS).
Em primeiro lugar, precisamos criar uma nova pasta com o nome do nosso componente dentro de src > components, e em seguida, devemos criar um novo arquivo chamado de index.tsx
, que vai armazenar o nosso componente em sí.
Vamos fazer isso juntos? 😋
Passo 1°) Dentro da pasta components, crie uma nova pasta que vai representar o seu componente. No meu caso, eu criei uma pasta chamada Header, que representa um cabeçalho:
Passo 2°) Dentro da pasta que você acabou de criar (Header), vamos criar o arquivo que representa o nosso componente. Para facilitar a importação dentro de outros módulos, vamos criá-lo com o nome de index.tsx
:
Não se esqueça de apagar o arquivo info.txt dessa pasta, ok?
Por fim, basta que criemos a lógica do nosso cabeçalho dentro do index.tsx
, no meu caso, eu criei meu componente da seguinte forma.
Header > index.tsx
:
const Header = () => {
return(
<header>
<h1>Este é o meu Cabeçalho 👻</h1>
</header>
);
}
export default Header;
Podemos perceber que inicialmente, o componente Header
não faz nenhum uso da tipagem do Typescript, até porque isso não se faz necessário.
Nos próximos tópicos, iremos criar componentes que trabalham com propriedades (props
), onde alí veremos o uso da tipagem do TS dentro dos nossos componentes 😉
Por enquanto, vamos importar esse componente para dentro do App.tsx
para que possamos usá-lo:
import Header from './components/Header';
function App(){
return(
<>
<Header />
</>
);
}
export default App;
Note que em nenhum momento fizemos o uso da tipagem do Typescript, uma vez que por enquanto, isso não foi preciso.
Criando variáveis estáticas dentro do nosso componente
A criação de variáveis dentro dos nossos componentes, não se difere muito daquilo que vimos na lição de Tipos e Dados.
Para vermos isso em prático, vamos criar um novo componente chamado de Informacoes
com a seguinte lógica.
Informacoes > index.jsx
:
import React from 'react';
const Informacoes: React.FC = () => {
const nome: string = 'João';
const idade: number = 30;
const humano: boolean = true;
const caracteristicas: { altura: number; peso: number; cor: string } = {
altura: 1.80,
peso: 75,
cor: 'azul',
};
const numerosDaSorte: number[] = [7, 13, 21];
return (
<div>
<h1>Informações</h1>
<p><strong>Nome:</strong> {nome}</p>
<p><strong>Idade:</strong> {idade}</p>
<p><strong>Humano:</strong> {humano ? 'Sim' : 'Não'}</p>
<p><strong>Características:</strong></p>
<ul>
<li><strong>Altura:</strong> {caracteristicas.altura} m</li>
<li><strong>Peso:</strong> {caracteristicas.peso} kg</li>
<li><strong>Cor:</strong> {caracteristicas.cor}</li>
</ul>
<p><strong>Números da Sorte:</strong></p>
<ul>
{numerosDaSorte.map((numero, index) => (
<li key={index}>{numero}</li>
))}
</ul>
</div>
);
};
export default Informacoes;
Perceba que todas as variáveis acima estão completamente tipadas, seguindo as regras do superset Typescript.
Observe também que o nosso TSX (tags HTML existentes dentro do return) não precisaram fazer o uso da tipagem, pois não foi necessário.
Só existem um porém... o que diabos é esse tal de React.FC?
O que é o React.FC?
O React.FC
(ou também conhecido como React.FunctionComponent
) é um tipo fornecido pela biblioteca TypeScript voltado aos nossos componentes feitos com ReactTS .
É com ele que dizemos ao Typescript, que estamos trabalhando com um funcional. (Lembre-se que no TS, tudo é tipado!).
E apesar dele não se fazer necessário no ReactJS, no ReactTS é bom fazer a declaração (já iremos ver o porque).
Apesar disso, ele fornece algumas vantagens em termos de tipagem, e ajuda a garantir que o componente esteja em conformidade com a definição esperada de um componente funcional.
Sendo assim, sempre que você for criar um novo componente dentro da sua aplicação feita com ReactTS, sempre declare o React.FC
🤓
Enquanto estivermos usando React + Typescript, vamos adotar esse tipo de padrão, ok?
Exato! Fiz isso propositalmente para você entender que o React.FC não é obrigatório. Entretanto, é bom fazermos o seu uso.
Criando estados dentro do nosso componente (useState)
Para fazer o uso dos componentes de estados (useState), na nossa aplicação, é bem simples.
Para isso, vamos criar um novo componente chamado de InformacoesEstados
.
InformacoesEstados > index.tsx
:
import React, { useState } from 'react';
const InformacoesEstados: React.FC = () => {
// Definindo os estados usando useState
const [nome, setNome] = useState<string>('João');
const [idade, setIdade] = useState<number>(30);
const [humano, setHumano] = useState<boolean>(true);
const [caracteristicas, setCaracteristicas] = useState<{
altura: number;
peso: number;
cor: string;
}>({
altura: 1.80,
peso: 75,
cor: 'azul',
});
const [numerosDaSorte, setNumerosDaSorte] = useState<number[]>([7, 13, 21]);
// Renderizando o componente
return (
<div>
<h1>Informações</h1>
<p><strong>Nome:</strong> {nome}</p>
<p><strong>Idade:</strong> {idade}</p>
<p><strong>Humano:</strong> {humano ? 'Sim' : 'Não'}</p>
<p><strong>Características:</strong></p>
<ul>
<li><strong>Altura:</strong> {caracteristicas.altura} m</li>
<li><strong>Peso:</strong> {caracteristicas.peso} kg</li>
<li><strong>Cor:</strong> {caracteristicas.cor}</li>
</ul>
<p><strong>Números da Sorte:</strong></p>
<ul>
{numerosDaSorte.map((numero, index) => (
<li key={index}>{numero}</li>
))}
</ul>
</div>
);
};
export default InformacoesEstados;
Note que dentro de cada useState
, nós realizamos a tipagem dentro do <>
.
O que vai de acordo com o que aprendemos na nossa lição de Tipos e Dados.
Recebendo propriedades dentro do nosso componente
Existem alguns componentes em ReactTS que vão precisar receber alguns dados, dados esses conhecidos como propriedades (props
).
Para vermos isso na prática, vamos criar um novo componente chamado de Propriedades
, que inicialmente vai receber dois valores: nome
e idade
, sendo um deles opcional:
Propriedades > index.tsx
:
import React from 'react';
const Propriedades: React.FC<{ nome: string; idade?: number }> = ({ nome, idade }) => {
return (
<div>
<h1>Nome: {nome}</h1>
{idade !== undefined ? <p>Idade: {idade}</p> : <p>Idade não informada</p>}
</div>
);
};
export default Propriedades;
Para usar este componente, basta inserí-lo dentro do App.tsx
da seguinte forma:
import Propriedades from './components/Propriedades';
function App(){
return(
<>
<Propriedades nome="João" idade={30} />
</>
);
}
export default App;
Nos comandos acima, podemos notar duas coisas:
1°) Dentro do comando React.FC nós informamos os tipos das propriedades que nós iremos receber. (nome: string; idade?: number
) É por isso que ele se faz necessário!
2°) Nós não precisamos realizar qualquer outro tipo de tipagem, tanto no componente, quanto no App.tsx
. O que indica que a tipagem é feita para ser usada somente no momento da declaração das nossas variáveis, componentes, funções e classes.
Veja como ficou o resultado final:
Recebendo childrens dentro do nosso componente
Por vezes, você vai precisar criar um componente que recebe outros componentes dentro dele. Tal comportamento conhecido como children
. Mas como ficaria isso no ReactTS?
Vamos criar um novo componente chamado de Bloco
, com a seguinte lógica:
Bloco > index.tsx
:
import React from 'react';
const Bloco: React.FC<{ children: React.ReactNode }> = ({ children }) => {
return (
<div>
{children}
</div>
);
};
export default Bloco;
Para usar este componente, basta adicioná-lo no seu App.tsx
da seguinte forma:
import Bloco from './components/Bloco';
function App(){
return(
<>
<Bloco>
<h1>Este é o meu Bloco</h1>
<p>Este é o meu bloco de texto</p>
</Bloco>
</>
);
}
export default App;
Veja como ficou o resultado final:
No caso do comando acima, o tipo da propriedade children
é React.ReactNode
, que representa qualquer coisa que o React pode renderizar (mostrar na tela do usuário).
Isso quer dizer que, sempre que nós formos receber um children
, ele precisa (obrigatóriamente) estar tipado como React.ReactNode
, ok?
Criando funções dentro dos nossos componentes
Como você já deve ter visto na lição que fala sobre funções na jornada do Typescript, aqui elas também precisam ser tipadas 😉
Vamos ver um exemplo abaixo de um componente chamado Funcoes
.
Funcoes > index.tsx
:
import React, { useState } from 'react';
const Funcoes: React.FC = () => {
// Estado para armazenar o valor retornado por uma função
const [resultado, setResultado] = useState<string>('');
// Função sem retorno
const mostrarMensagem = (mensagem: string): void => {
console.log(mensagem);
// A função sem retorno pode realizar ações como logging ou manipulação de dados
};
// Função com retorno
const calcularSoma = (a: number, b: number): number => {
return a + b;
};
// Função com retorno que altera o estado
const atualizarResultado = (): void => {
const soma = calcularSoma(5, 7);
setResultado(`A soma é: ${soma}`);
};
// Função que retorna um JSX
const criarElemento = (texto: string): JSX.Element => {
return <p>{texto}</p>;
};
return (
<div>
<h1>Funções em React</h1>
<button onClick={() => mostrarMensagem('Olá, Mundo!')}>Mostrar Mensagem</button>
<button onClick={atualizarResultado}>Calcular Soma</button>
<div>
{criarElemento('Este é um elemento criado pela função criarElemento.')}
</div>
<div>
<h2>{resultado}</h2>
</div>
</div>
);
};
export default Funcoes;
E não se esqueça de importá-lo dentro do App.tsx
:
import Funcoes from './components/Funcoes';
function App(){
return(
<>
<Funcoes />
</>
);
}
export default App;
Veja como ficou o resultado final:
Dando uma pequena analisada no código acima, a sintaxe das nossas funções, não se diferem muito daquilo que aprendemos na lição de funções.
A única coisa de diferente que nós ainda não vimos nessa jornada, é o uso do comando JSX.Element
.
O comando JSX.Element
é um tipo específico no TypeScript que representa um elemento JSX (ou seja, um elemento React) e que pode ser retornado.
Ele é bastante utilizado para definir um componente, ou uma função que é capaz de retornar um elemento em React, e que será renderizado na interface do usuário (dentro do comando return).
Criando um componente de formulários
Qualquer sistema que se preze, precisa de um formulário, não é mesmo?
E é obvío que eu não poderia pular esta etapa 😅
Para analisarmos o comportamento dos nossos formulários em ReactTS, vamos criar um novo componente chamado de Formularios
.
Formularios > index.tsx
:
import React, { useState } from 'react';
const Formularios: React.FC = () => {
// Estados para armazenar os valores dos campos
const [texto, setTexto] = useState<string>('');
const [opcao, setOpcao] = useState<string>('opcao1');
const [comentario, setComentario] = useState<string>('');
const [checkbox, setCheckbox] = useState<boolean>(false);
// Função para lidar com a mudança do campo de texto
const handleTextoChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setTexto(event.target.value);
};
// Função para lidar com a mudança da seleção
const handleOpcaoChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setOpcao(event.target.value);
};
// Função para lidar com a mudança da área de texto
const handleComentarioChange = (event: React.ChangeEvent<HTMLTextAreaElement>) => {
setComentario(event.target.value);
};
// Função para lidar com a mudança do checkbox
const handleCheckboxChange = (event: React.ChangeEvent<HTMLInputElement>) => {
setCheckbox(event.target.checked);
};
// Função para lidar com o envio do formulário
const handleSubmit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); // Evita o envio do formulário padrão
console.log('Texto:', texto);
console.log('Opção selecionada:', opcao);
console.log('Comentário:', comentario);
console.log('Checkbox:', checkbox);
};
return (
<div>
<h1>Formulários em React</h1>
<form onSubmit={handleSubmit}>
<div>
<label htmlFor="texto">Texto:</label>
<input
type="text"
id="texto"
value={texto}
onChange={handleTextoChange}
/>
</div>
<div>
<label htmlFor="opcao">Opção:</label>
<select
id="opcao"
value={opcao}
onChange={handleOpcaoChange}
>
<option value="opcao1">Opção 1</option>
<option value="opcao2">Opção 2</option>
<option value="opcao3">Opção 3</option>
</select>
</div>
<div>
<label htmlFor="comentario">Comentário:</label>
<textarea
id="comentario"
value={comentario}
onChange={handleComentarioChange}
/>
</div>
<div>
<label htmlFor="checkbox">Aceito os termos:</label>
<input
type="checkbox"
id="checkbox"
checked={checkbox}
onChange={handleCheckboxChange}
/>
</div>
<button type="submit">Enviar</button>
</form>
</div>
);
};
export default Formularios;
Não se esqueça de chamar esse componente dentro do App.tsx
:
import Formularios from './components/Formularios';
function App(){
return(
<>
<Formularios />
</>
);
}
export default App;
Veja como ficou o resultado final:
Bem feinho, né? Mas isso não importa... o que importa é entendermos o conceito das funcionalidades. Estilização a gente se preocupa depois 😂
No comando acima, notamos a presença de alguns comandos novos, onde cada um deles representam uma interface
!
O React.ChangeEvent
é um tipo genérico de evento usado em React para descrever eventos de alteração, como a mudança de valor em um campo de formulário. Ele é usado para manipular eventos relacionados com ampos de entrada, seleção, áreas de texto, etc.
O HTMLInputElement
é uma interface do DOM
que representa um elemento <input>
. Ele fornece propriedades e métodos específicos para manipular campos de entrada, como textos, números, e checkboxes.
O HTMLSelectElement
é uma interface do DOM
que representa um elemento <select>
. Ele é usado para criar menus suspensos e fornece propriedades e métodos para manipular opções e o valor selecionado.
O HTMLTextAreaElement
é uma interface do DOM
que representa um elemento <textarea>
. Ele é usado para criar áreas de texto multilinha para a entrada de dados.
O HTMLFormElement
é uma interface do DOM
que representa um elemento <form>
. Ele fornece métodos e propriedades para manipular e interagir com o formulário, incluindo a capacidade de enviar o formulário e acessar os dados dos campos.
E sim, qualquer alteração de eventos que acontecer na nossa aplicação, nós somos obrigados a fazer a tipagem dessa forma 🫢
Criando componentes profissionais com Interfaces e Types
Em tópicos anteriores, você viu que eu fui tipando todos os componentes de forma manual, certo?
Só que... no atual mercado de trabalho, você irá notar que a maioria dos projetos, não costumam tipar da maneira como foi apresentada em meus códigos anteriores.
Visando o cumprimento das boas práticas, a maioria dos desenvolvedores, preferem criar Interfaces ou Types para escrever as propriedades dos seus componentes, evitando assim uma declaração manual.
Por exemplo, vamos criar um novo componente chamado Botao
, que faz o uso de uma interface
:
Botao > index.tsx
:
import React from 'react';
// Define a interface para as propriedades do componente
interface BotaoProps {
texto: string;
onClick: () => void;
}
// Componente usando a interface para as propriedades
const Botao: React.FC<BotaoProps> = ({ texto, onClick }) => {
return <button onClick={onClick}>{texto}</button>;
};
export default Botao;
Note que passamos a interface (BotaoProps
) para dentro do componente Botao
, o que torna nosso componente mais organizado e estruturado.
Veja um outro exemplo do mesmo componente fazendo o uso do Type
:
import React from 'react';
// Define o tipo para as propriedades do componente
type BotaoProps = {
texto: string;
onClick: () => void;
};
// Componente usando o tipo para as propriedades
const Botao: React.FC<BotaoProps> = ({ texto, onClick }) => {
return <button onClick={onClick}>{texto}</button>;
};
export default Botao;
Legal, não acha?
Lembrando que nós podemos aplicar a mesma estratégia durante a declaração das nossas funções, e você já viu isso na lição que fala sobre elas 😉
Para melhorar mais ainda a nossa organização, há desenvolvedores que preferem separar a declaração de uma interface
ou type
fora do componente que às utiliza.
Isso é especialmente útil em projetos maiores onde o código pode se tornar complexo, e difícil de se gerenciar, isto é, se todas as declarações e componentes estiverem no mesmo arquivo.
Pensando nisso, existem algumas formas de se fazer essa separação, vejamos algumas delas:
Método 1°) Separando Interfaces e Types em uma pasta específica.
A ideia aqui, é criar um diretório próprio (dentro da pasta src) chamado de types e outro de interfaces.
A pasta types vai armazenar todos os nossos types
, e a pasta interfaces vai armazenar todas as nossas interfaces
.
Por fim, basta criar seus arquivos .ts
com um nome que representam uma interface
ou um type
.
Por exemplo, vamos supor que nós queremos separar a lógica da interface do nosso componente Botao
. Para isso, eu precisei criar um arquivo chamado ButtonInterface.ts
dentro da pasta Interface
, contendo a seguinte lógica.
Interfaces > ButtonInterface.ts
:
export interface ButtonProps {
texto: string;
onClick: () => void;
}
Já dentro do seu componente (Botao
), basta importá-lo da seguinte forma:
import React from 'react';
import { ButtonProps } from '../../interfaces/ButtonInterface'; // Importando a interface
// Componente Botao usando a interface importada
const Botao: React.FC<ButtonProps> = ({ texto, onClick }) => {
return <button onClick={onClick}>{texto}</button>;
};
export default Botao;
O mesmo resultado pode atingido por meio de um type, observe:
Types > ButtonTypes.ts
:
export type ButtonProps = {
texto: string;
onClick: () => void;
};
O processo de importação do Type
segue a mesma lógica da Interface
🙂
Método 2°) Separando Interfaces e Types dentro da mesma pasta do componente.
Em vez de você criar novas pastas dentro do seu projeto, você poderia criar novos arquivos como buttonProps.interface.ts
ou buttonProps.type.ts
, dentro da mesma pasta onde está localizado o index.tsx
do seu componente. Por exemplo:
Botao > buttonProps.interface.ts
:
export interface ButtonProps {
texto: string;
onClick: () => void;
}
Botao > buttonProps.type.ts
:
export type ButtonProps = {
texto: string;
onClick: () => void;
};
E para importá-lo para dentro do seu componente, basta fazer o import
da seguinte forma:
Botao > index.tsx
:
import React from 'react';
import { ButtonProps } from './buttonprops.interface';
// Componente usando a interface para as propriedades
const Botao: React.FC<ButtonProps> = ({ texto, onClick }) => {
return <button onClick={onClick}>{texto}</button>;
};
export default Botao;
Ficou bem mais organizado, não acha?
O funcionamento de um componente padrão do ReactTS
Antes de continuarmos, gostaria que você desse uma olhada na sintaxe abaixo:
import react from 'react';
interface Props {
//Declare aqui suas props
}
const MeuComponente = (props: Props) => {
//Declare aqui seus estados e funções
return(
<div>
{/* Declare a lógica do seu componente aqui */}
<div>
);
}
export default MeuComponente;
O código que você acabou de analisar, nada mais é do que a estrutura inicial de um componente feito com ReactTS.
Visando manter o mesmo padrão de mercado, gostaria que todos os seus novos componentes fossem criados seguindo a lógica anterior, pode ser? (você vai me agradecer por isso mais tarde).
Ou seja, sempre crie uma interface que vai interagir com os possíveis props
que você pode receber no seu componente. (E se elas estiverem organizadas em arquivos separados, melhor ainda).
Observação: Não esqueça de declarar o comando React.FC
seguindo a sintaxe que você aprendeu anteriormente.
Agora, nós iremos ver um exemplo simples de um componente que faz o uso da sintaxe que analisamos acima:
import react from 'react';
interface Props {
nome: string;
idade: number;
}
const MeuComponente = (props: Props) => {
//Declare aqui seus estados e funções
return(
<div>
<p>Nome: {props.nome}, Idade: {props.idade}</p>
<div>
);
}
export default MeuComponente;
Você também pode aplicar o conceito da desestruturação da seguinte forma:
import react from 'react';
interface Props {
nome: string;
idade: number;
}
const MeuComponente = ({ nome, idade } : Props) => {
//Declare aqui seus estados e funções
return(
<div>
<p>Nome: {nome}, Idade: {idade}</p>
<div>
);
}
export default MeuComponente;
Viu como é fácil?
Arquivos da lição
Os arquivos desta lição podem ser encontrados no repositório do GitHub por meio deste link.
Conclusão
Nesta lição você aprendeu a forma com que criamos nossos componentes com ReactTS.
Na próxima lição, daremos uma olhada mais a fundo sobre o funcionamento dos estados e Context API.
Até lá 🤩