Componentes React com TypeScript

Componentes React com TypeScript

Quer embarcar em uma jornada empolgante pelo React com um toque de aventura ao estilo pirata de One Piece? Neste artigo, vamos explorar como criar componentes React com TypeScript e desbravar mais uma parte do oceano do desenvolvimento front-end.

Personagem Luffy do anime One Piece rindo.

Vamos aprender a definir tipos para nossos componentes, navegar pelas propriedades (props) como verdadeiros capitães e desvendar os segredos dos estados (state). Além disso, vamos dominar o poder dos generics para criar componentes flexíveis e personalizados. Bora lá?

JavaScript Vs TypeScript

React é um framework JavaScript que nos permite criar interfaces de usuário de forma interativa e reativa. Ele é baseado na ideia de componentes, que são elementos reutilizáveis que podem receber dados e renderizar uma parte da interface.

Já o TypeScript é um superconjunto do Javascript que nos permite definir tipos estáticos, orientação a objetos e facilita a escrita de um código de fácil legibilidade. Ele também nos ajuda a identificar e evitar erros no nosso código.

Quando usamos TypeScript com React, ganhamos a vantagem de definir bem os tipos dos nossos componentes. Além disso, nos beneficiamos do princípio de fail fast, que nos permite detectar erros mais cedo e melhorar a qualidade do código.

Para iniciarmos nossa aventura e criar um projeto utilizando React com TypeScript, podemos usar o comando:

npm create vite@latest my-react-app -- --template react-ts

Isso vai gerar uma estrutura básica do projeto, com as configurações necessárias para usar o TypeScript. Podemos então começar a criar nossos componentes dentro da pasta src. Essa estrutura será nosso barco, onde vamos passar por várias aventuras dentro dela.

Banner promocional da Alura, com um design futurista em tons de azul, apresentando o texto

Componente com TypeScript

Vamos criar um componente simples chamado Pirata, que vai representar um personagem de One Piece.

Para criar um componente usando React com TypeScript, podemos usar duas formas: função ou classe. Neste exemplo, vamos usar função. Para isso, criamos um arquivo chamado Pirata.tsx dentro da pasta src/Components. O código do componente é o seguinte:

type PirataProps = {
  nome: string;
  cargo: string;
  imagem: string;
};

Para construir esse componente definimos um tipo chamado PirataProps, que vai representar as propriedades que o nosso componente vai receber. Neste caso, são três: nome, cargo e imagem, todos do tipo string.

function Pirata({nome, cargo, imagem} : PirataProps) {
 return (
    <div className="pirata">
      <img src={imagem} alt={nome} />
      <h3>{nome}</h3>
      <p>{cargo}</p>
    </div>
  );
}

export default Pirata;

Em seguida, criamos uma função chamada Pirata, que é uma função que recebe um objeto com as propriedades definidas no tipo PirataProps e retorna um elemento JSX, que é a sintaxe usada pelo react para renderizar a interface.

Dentro da função, usamos a desestruturação de objetos para pegar as propriedades nome, cargo e imagem do objeto recebido. Em seguida, retornamos um elemento JSX que contém uma div com a classe pirata, uma imagem com o src e o alt iguais às propriedades image e name, respectivamente, um h3 com o nome do personagem e um p com o seu papel na tripulação.

Por fim, exportamos o nosso componente para poder usá-lo em outros arquivos. Para utilizar o componente, vamos no arquivo App.tsx e iremos aplicar o componente Pirata, enviando as propriedades necessárias. Por exemplo:

function App() {
  return (
    <>
<Pirata nome="Chopper" cargo="Médico" imagem="https://i.pinimg.com/564x/ec/5b/57/ec5b57209323835ff8513238a48811eb.jpg" />
    </>
  )
}
export default App

E o resultado na tela será o seguinte, após aplicar algumas estilizações no componente:

Um cartão com uma foto do personagem Chopper do anime One Piece, em seguida está escrito seu nome e seu cargo que é Médico.

Eventos e estados

Em nossa jornada de construir componentes com TypeScript, um dos desafios que encontraremos é aplicar interatividade por meio de estados e eventos. Como podemos definir tipos para lidar com essas funcionalidades de maneira segura e precisa?

Vamos encarar esse desafio na prática, criando um componente chamado Recompensa. Este componente exibirá o valor da recompensa de cada personagem e oferecerá a capacidade de aumentar ou diminuir esse valor, usando tipos TypeScript para garantir a integridade dos dados e a confiabilidade da interatividade.

Para criar o componente Recompensa, vamos criar um arquivo chamado Recompensa.tsx dentro da pasta src/Components. O código do componente é o seguinte:

import { useState } from 'react';

type RecompensaProps = {
  valorRecompensa: number;
};

Vamos analisar o código do componente. Primeiro, importamos o hook useState, que vamos usar para gerenciar o estado do componente. Depois, definimos um tipo chamado RecompensaProps, que vai representar a propriedade que o nosso componente vai receber. Neste caso, sendo o valorRecompensa como tipo number.

function Recompensa({ valorRecompensa }: RecompensaProps) {

}

Em seguida começamos a definir a função do componente Recompensa. Ela recebe a propriedade valorRecompensa como argumento, desestruturando diretamente dos props. O TypeScript nos ajuda a entender que estamos esperando essas propriedades com base no tipo RecompensaProps. Se tentássemos acessar uma propriedade que não existe ou que possui um tipo incorreto, o TypeScript nos alertaria durante a fase de desenvolvimento.

function Recompensa({ valorRecompensa }: RecompensaProps) {
  const [recompensa, setRecompensa] = useState(valorRecompensa);
  const [novoValor, setNovoValor] = useState('');
}

Dentro da função, usamos o useState para criar dois estados: recompensa e novoValor. O TypeScript nos ajuda a especificar os tipos desses estados de forma explícita, tornando mais claro o que esperamos armazenar neles. Por exemplo, recompensa é do tipo number, e novoValor é uma string. O TypeScript nos impede de atribuir um tipo diferente a esses estados.

  const handleChange = (evento: React.ChangeEvent<HTMLInputElement>) => {
    const valorDigitado = evento.target.value;
    setNovoValor(valorDigitado);
  };

Depois, definimos a função handleChange que lida com a mudança de valor na entrada de texto. O TypeScript nos fornece o tipo exato do evento, que é o React.ChangeEvent, e da propriedade value do elemento de entrada.

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
     setRecompensa(novoValorNum);
     setNovoValor('');
};

Além disso, criamos a função handleSubmit que lida com o envio do formulário. O TypeScript nos permite especificar que estamos tratando de um evento de formulário (React.FormEvent) e, ao mesmo tempo, nos ajuda a verificar e converter corretamente o novo valor inserido pelo usuário em um número.

//…
  return (
    <div className="recompensa">
      <h3>Recompensa</h3>
      <p>{recompensa.toLocaleString()} berries</p>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          value={novoValor}
          onChange={handleChange}
          placeholder="Novo valor"
        />
        <button type="submit">Atualizar</button>
      </form>
    </div>
  );
}

export default Recompensa;

Por fim, retornamos um elemento JSX que contém uma div com a classe recompensa, um h3 com o texto Recompensa, um p com o valor da recompensa formatado com o método toLocaleString, que adiciona separadores de milhar e casa decimal, e um formulário com um campo para digitar o valor da recompensa e um botão para enviar o valor do campo de digitação. O resultado final do código seria:

import { useState } from 'react';

type RecompensaProps = {
  nome: string;
  valorRecompensa: number;
};

function Recompensa({ nome, valorRecompensa }: RecompensaProps) {
  const [recompensa, setRecompensa] = useState(valorRecompensa);
  const [novoValor, setNovoValor] = useState('');

  const handleChange = (evento: React.ChangeEvent<HTMLInputElement>) => {
    const valorDigitado = evento.target.value;
    setNovoValor(valorDigitado);
  };

  const handleSubmit = (e: React.FormEvent) => {
    e.preventDefault();
     setRecompensa(novoValorNum);
     setNovoValor('');
};

  return (
    <div className="recompensa">
      <h3>Recompensa</h3>
      <p>{recompensa.toLocaleString()} berries</p>
      <form onSubmit={handleSubmit}>
        <input
          type="number"
          value={novoValor}
          onChange={handleChange}
          placeholder="Novo valor"
        />
        <button type="submit">Atualizar</button>
      </form>
    </div>
  );
}

export default Recompensa;

Atribuir tipos na manipulação de eventos ajuda a prevenir erros comuns que podem ocorrer durante a manipulação de eventos, como tentativas de acessar propriedades inexistentes ou passar argumentos do tipo errado para os manipuladores de eventos.

A tipagem explícita também torna o código mais fácil de entender e manter, melhorando a legibilidade e a documentação. Além disso, ferramentas de desenvolvimento que suportam tipagem, como o TypeScript, fornecem feedback imediato e detectam erros em tempo de compilação, acelerando o processo de depuração.

Por fim, no nosso componente, exportamos ele para poder usá-lo em outros arquivos. Um exemplo de utilização seria colocar dentro do cartão do componente Pirata:

<Recompensa valorRecompensa={10} />

E ao aplicar algumas propriedades de estilização, conseguimos chegar no seguinte resultado:

Um cartão com uma foto do personagem Chopper do anime One Piece, em seguida está escrito seu nome e seu cargo que é Médico, abaixo há a um titulo recompensa com o valor de 10 berries, um campo de digitação com o texto novo valor e um botão com texto atualizar.

Generics

Agora, ao continuar sua aventura, você se depara com um problema intrigante. Você precisa criar um componente chamado Fruta, que será responsável por exibir informações sobre uma variedade de frutas do mundo de One Piece. Até aqui, tudo bem. No entanto, há um desafio: você não sabe exatamente que tipo de fruta será exibida em cada instância do componente. Como lidar com essa incerteza?

Para enfrentar esse problema, recorremos a um recurso poderoso do TypeScript: Generics. Com os Generics, podemos criar componentes flexíveis que podem lidar com diferentes tipos de dados de forma segura. Vamos dar uma olhada na solução por meio da criação do componente Fruta.

Para criar o componente Fruta, vamos criar um arquivo chamado Fruta.tsx dentro da pasta src/Components. O código do componente é o seguinte:

type FrutaTipos = 'Paramecia' | 'Zoan' | 'Logia';

Neste trecho, estamos criando um tipo chamado FrutaTipos que é uma união de três strings: 'Paramecia', 'Zoan' e 'Logia'. Este tipo é usado para representar os tipos de frutas especiais no código.

type FrutaProps<T extends FrutaTipos> = {
    nome: string;
    imagem: string;
    tipo: T;
};

Aqui, estamos definindo um tipo genérico FrutaProps que recebe um tipo T como parâmetro. Este tipo descreve as propriedades que o componente Fruta aceita. Ele inclui três propriedades:

  • nome: Uma string que representa o nome da fruta.
  • imagem: Uma string que representa o URL da imagem da fruta.
  • tipo: Uma propriedade com o tipo T, que é um dos tipos definidos em FrutaTipos. Isso significa que o tipo da fruta deve ser uma das opções válidas.
function Fruta({ nome, imagem, tipo }: FrutaProps<FrutaTipos>): JSX.Element {
    return (
        <div className="fruta">
            <img src={imagem} alt="Imagem da fruta" />
            <h3>{nome}</h3>
            <p>{tipo}</p>
        </div>
    );
}
export default Fruta;

Aqui, estamos criando o componente Fruta que recebe as propriedades definidas em FrutaProps. Isso garante que o componente só aceitará propriedades com os tipos corretos. O componente Fruta renderiza as propriedades recebidas da seguinte forma:

  • Uma imagem usando o URL especificado na propriedade imagem.
  • O nome da fruta é renderizado dentro de um elemento <h3> usando a propriedade nome.
  • O tipo da fruta é renderizado dentro de um elemento <p> usando a propriedade tipo.

Resultado Completo do Código:

type FrutaTipos = 'Paramecia' | 'Zoan' | 'Logia';

type FrutaProps<T extends FrutaTipos> = {
    nome: string;
    imagem: string;
    tipo: T;
};

function Fruta({ nome, imagem, tipo }: FrutaProps<FrutaTipos>): JSX.Element {
    return (
        <div className="fruta">
            <img src={imagem} alt="Imagem da fruta" />
            <h3>{nome}</h3>
            <p>{tipo}</p>
        </div>
    );
}

export default Fruta;

Por fim, exportamos o nosso componente para poder usá-lo em outros arquivos. Para visualizar o resultado na tela, podemos ir no App.tsx e aplicar o componente, enviando as props adequadas. Por exemplo:

<Fruta nome="Gomu Gomu no Mi" tipo="Paramecia" imagem="https://pm1.aminoapps.com/6309/b9d6ca1c72f20877d9ee0c3aa3d2345502c10a64_hq.jpg" />

O resultado na tela, após aplicar estilizações, seria:

Um cartão com uma imagem de uma fruta roxa dentro de um baú com o título Gomu Gomu no Mi e descrição Paramecia.

Conclusão

Nesta jornada exploramos os mares agitados do desenvolvimento front-end com React e TypeScript, todos com um toque de inspiração de One Piece. De definir os tipos dos componentes a navegar habilmente pelas propriedades, de desvendar os segredos dos estados a dominar o poder dos generics, nossa tripulação de desenvolvedores está agora mais bem equipada do que nunca.

Assim como o bando de piratas do Chapéu de Palha enfrenta desafios e obstáculos em sua busca pelo One Piece, nós também enfrentamos desafios em nossos projetos de desenvolvimento. Mas, com as ferramentas certas em nossas mãos, podemos superar qualquer tempestade de código que a vida nos lance.

Lembre-se de que criar componentes React com TypeScript não é apenas uma boa prática, mas também uma forma de garantir um código mais seguro, legível e manutenível.

Personagem Nami do anime One Piece sorrindo na neve.

Além disso, você sabia que você pode contar com o Tech Guide para ser sua bússola nessa jornada? Não deixe de conferir!

Agradeço por embarcar conosco nesta busca pelo conhecimento. Continue explorando, continue codificando e, quem sabe, talvez você também encontre o seu tesouro "One Piece" no mundo do desenvolvimento.

Até a próxima aventura!

Mônica Mazzochi Hillman
Mônica Mazzochi Hillman

Bacharela em Tecnologias Digitais, especialista em User Experience e pós graduanda em Docência e Performance na Educação a Distância com experiência em suporte técnico de T.I e com tecnologias front-end. Atualmente é Tech Community Manager na Magalu Cloud e instrutora na Alura. Nas horas vagas gosta de assistir animes e produções da marvel, ouvir kpop e post-hardcore, jogar Valorant e TFT.

Veja outros artigos sobre Front-end