Configurando o TypeScript para projetos modernos do React usando o Webpack
Publicados: 2022-03-10Nesta era de desenvolvimento de software, o JavaScript pode ser usado para desenvolver quase qualquer tipo de aplicativo. No entanto, o fato de o JavaScript ser tipado dinamicamente pode ser uma preocupação para a maioria das grandes empresas, por causa de seu recurso de verificação de tipo solto.
Felizmente, não temos que esperar até que o Ecma Technical Committee 39 introduza um sistema de tipo estático em JavaScript. Podemos usar o TypeScript em vez disso.
JavaScript, sendo tipado dinamicamente, não está ciente do tipo de dados de uma variável até que essa variável seja instanciada em tempo de execução. Os desenvolvedores que escrevem grandes programas de software podem ter a tendência de reatribuir uma variável, declarada anteriormente, a um valor de um tipo diferente, sem nenhum aviso ou problema, resultando em bugs muitas vezes ignorados.
Neste tutorial, aprenderemos o que é o TypeScript e como trabalhar com ele em um projeto React. Ao final, teremos construído um projeto que consiste em um aplicativo de seleção de episódios para o programa de TV Money Heist , usando TypeScript e ganchos atuais do tipo React ( useState , useEffect , useReducer , useContext ). Com esse conhecimento, você pode experimentar o TypeScript em seus próprios projetos.
Este artigo não é uma introdução ao TypeScript. Portanto, não passaremos pela sintaxe básica do TypeScript e do JavaScript. No entanto, você não precisa ser um especialista em nenhuma dessas linguagens para acompanhar, porque tentaremos seguir o princípio KISS (simples, estúpido).
O que é TypeScript?
Em 2019, o TypeScript foi classificado como a sétima linguagem mais usada e a quinta linguagem de crescimento mais rápido no GitHub. Mas o que exatamente é o TypeScript?
De acordo com a documentação oficial, TypeScript é um superconjunto tipado de JavaScript que compila para JavaScript simples. Ele é desenvolvido e mantido pela Microsoft e pela comunidade de código aberto.
“Superconjunto” neste contexto significa que a linguagem contém todos os recursos e funcionalidades do JavaScript e muito mais. TypeScript é uma linguagem de script tipada.
Ele oferece aos desenvolvedores mais controle sobre sua base de código por meio de anotação de tipo, classes e interface, poupando os desenvolvedores de ter que corrigir manualmente bugs irritantes no console.
O TypeScript não foi criado para alterar o JavaScript. Em vez disso, ele expande o JavaScript com novos recursos valiosos. Qualquer programa escrito em JavaScript simples também será executado conforme esperado no TypeScript, incluindo aplicativos móveis multiplataforma e back-ends em Node.js.
Isso significa que você também pode escrever aplicativos React no TypeScript, como faremos neste tutorial.
Por que TypeScript?
Talvez você não esteja convencido de abraçar a bondade do TypeScript. Vamos considerar algumas de suas vantagens.
Menos erros
Não podemos eliminar todos os bugs em nosso código, mas podemos reduzi-los. O TypeScript verifica os tipos em tempo de compilação e gera erros se o tipo de variável for alterado.
Ser capaz de encontrar esses erros óbvios, porém frequentes, logo no início torna muito mais fácil gerenciar seu código com tipos.
Refatorar é mais fácil
Você provavelmente quer refatorar muitas coisas, mas porque eles tocam em tantos outros códigos e muitos outros arquivos, você tem medo de modificá-los.
No TypeScript, essas coisas geralmente podem ser refatoradas com apenas um clique no comando “Renomear símbolo” em seu ambiente de desenvolvimento integrado (IDE).

Em uma linguagem tipada dinamicamente como JavaScript, a única maneira de refatorar vários arquivos ao mesmo tempo é com a função tradicional “pesquisar e substituir” usando expressões regulares (RegExp).
Em uma linguagem de tipagem estática como o TypeScript, “pesquisar e substituir” não é mais necessário. Com comandos do IDE, como “Localizar todas as ocorrências” e “Renomear símbolo”, você pode ver todas as ocorrências no aplicativo de determinada função, classe ou propriedade de uma interface de objeto.
O TypeScript o ajudará a encontrar todas as instâncias do bit refatorado, renomeá-lo e alertá-lo com um erro de compilação caso seu código tenha algum tipo de incompatibilidade após a refatoração.
O TypeScript tem ainda mais vantagens do que abordamos aqui.
Desvantagens do TypeScript
O TypeScript certamente tem suas desvantagens, mesmo considerando os recursos promissores destacados acima.
Uma falsa sensação de segurança
O recurso de verificação de tipo do TypeScript geralmente cria uma falsa sensação de segurança entre os desenvolvedores. A verificação de tipos de fato nos avisa quando algo está errado com nosso código. No entanto, os tipos estáticos não reduzem a densidade geral de bugs.
Portanto, a força do seu programa dependerá do uso do TypeScript, porque os tipos são escritos pelo desenvolvedor e não verificados em tempo de execução.
Se você estiver procurando o TypeScript para reduzir seus bugs, considere o desenvolvimento orientado a testes.
Sistema de digitação complicado
O sistema de digitação, embora seja uma ótima ferramenta em muitos aspectos, às vezes pode ser um pouco complicado. Essa desvantagem decorre de ser totalmente interoperável com JavaScript, o que deixa ainda mais espaço para complicações.
No entanto, o TypeScript ainda é JavaScript, portanto, é importante entender o JavaScript.
Quando usar o TypeScript?
Eu aconselho você a usar o TypeScript nos seguintes casos:
- Se você deseja criar um aplicativo que será mantido por um longo período , recomendo fortemente começar com o TypeScript, porque ele promove a autodocumentação do código, ajudando outros desenvolvedores a entender seu código facilmente quando ingressarem em sua base de código .
- Se você precisar criar uma biblioteca , considere escrevê-la em TypeScript. Isso ajudará os editores de código a sugerir os tipos apropriados aos desenvolvedores que estão usando sua biblioteca.
Nas últimas seções, equilibramos os prós e contras do TypeScript. Vamos para o assunto do dia: configurar o TypeScript em um projeto React moderno .
Começando
Existem várias maneiras de configurar o TypeScript em um projeto React. Neste tutorial, abordaremos apenas dois.
Método 1: Criar React App + TypeScript
Cerca de dois anos atrás, a equipe do React lançou o Create React App 2.1, com suporte a TypeScript. Portanto, talvez você nunca precise fazer nenhum trabalho pesado para colocar o TypeScript em seu projeto.

Para iniciar um novo projeto Create React App, você pode executar este…
npx create-react-app my-app --folder-name… ou isto:
yarn create react-app my-app --folder-name Para adicionar TypeScript a um projeto Create React App, primeiro instale-o e seus respectivos @types :
npm install --save typescript @types/node @types/react @types/react-dom @types/jest… ou:
yarn add typescript @types/node @types/react @types/react-dom @types/jest Em seguida, renomeie os arquivos (por exemplo, index.js para index.tsx ) e reinicie seu servidor de desenvolvimento !
Isso foi rápido, não foi?
Método 2: configurar o TypeScript com o Webpack
Webpack é um empacotador de módulo estático para aplicativos JavaScript. Ele pega todo o código do seu aplicativo e o torna utilizável em um navegador da web. Os módulos são blocos reutilizáveis de código criados a partir do JavaScript, node_modules , imagens e estilos CSS do seu aplicativo, que são empacotados para serem facilmente usados em seu site.
Criar um novo projeto
Vamos começar criando um novo diretório para nosso projeto:
mkdir react-webpack cd react-webpackUsaremos o npm para inicializar nosso projeto:
npm init -y O comando acima irá gerar um arquivo package.json com alguns valores padrão. Vamos também adicionar algumas dependências para webpack, TypeScript e alguns módulos específicos do React.
Instalando Pacotes
Por fim, precisaríamos instalar os pacotes necessários. Abra sua interface de linha de comando (CLI) e execute isto:
#Installing devDependencies npm install --save-dev @types/react @types/react-dom awesome-typescript-loader css-loader html-webpack-plugin mini-css-extract-plugin source-map-loader typescript webpack webpack-cli webpack-dev-server #installing Dependencies npm install react react-dom Vamos também adicionar manualmente alguns arquivos e pastas diferentes em nossa pasta react-webpack :
- Adicione
webpack.config.jspara adicionar configurações relacionadas ao webpack. - Adicione
tsconfig.jsonpara todas as nossas configurações de TypeScript. - Adicione um novo diretório,
src. - Crie um novo diretório,
components, na pastasrc. - Por fim, adicione
index.html,App.tsxeindex.tsxna pasta decomponents.
Estrutura do projeto
Assim, nossa estrutura de pastas ficará assim:
├── package.json ├── package-lock.json ├── tsconfig.json ├── webpack.config.js ├── .gitignore └── src └──components ├── App.tsx ├── index.tsx ├── index.htmlComece a adicionar algum código
Começaremos com index.html :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>React-Webpack Setup</title> </head> <body> <div></div> </body> </html> Isso criará o HTML, com um div vazio com um ID de output .
Vamos adicionar o código ao nosso componente React App.tsx :
import * as React from "react"; export interface HelloWorldProps { userName: string; lang: string; } export const App = (props: HelloWorldProps) => ( <h1> Hi {props.userName} from React! Welcome to {props.lang}! </h1> ); Criamos um objeto de interface e o HelloWorldProps , com userName e lang tendo um tipo de string .
Passamos props para o nosso componente App e exportamos.
Agora, vamos atualizar o código em index.tsx :
import * as React from "react"; import * as ReactDOM from "react-dom"; import { App } from "./App"; ReactDOM.render( <App userName="Beveloper" lang="TypeScript" />, document.getElementById("output") ); Acabamos de importar o componente App para index.tsx . Quando o webpack vê qualquer arquivo com a extensão .ts ou .tsx , ele irá transpilar esse arquivo usando a biblioteca awesome-typescript-loader.
Configuração do TypeScript
Em seguida, adicionaremos algumas configurações ao tsconfig.json :
{ "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "src/components/index.tsx" ] } Vejamos também as diferentes opções que adicionamos ao tsconfig.json :
-
compilerOptionsRepresenta as diferentes opções do compilador. -
jsx:reactAdiciona suporte para JSX em arquivos.tsx. -
libAdiciona uma lista de arquivos de biblioteca à compilação (por exemplo, usares2015nos permite usar a sintaxe ECMAScript 6). -
moduleGera o código do módulo. -
noImplicitAnyGera erros para declarações comanytipo implícito. -
outDirRepresenta o diretório de saída. -
sourceMapGera um arquivo.map, que pode ser muito útil para depurar o aplicativo. -
targetRepresenta a versão do ECMAScript de destino para a qual transpilar nosso código (podemos adicionar uma versão com base em nossos requisitos específicos do navegador). -
includeUsado para especificar a lista de arquivos a ser incluída.
Configuração do Webpack
Vamos adicionar algumas configurações do webpack ao webpack.config.js .
const path = require("path"); const HtmlWebpackPlugin = require("html-webpack-plugin"); const MiniCssExtractPlugin = require("mini-css-extract-plugin"); module.exports = { entry: "./src/components/index.tsx", target: "web", mode: "development", output: { path: path.resolve(\__dirname, "build"), filename: "bundle.js", }, resolve: { extensions: [".js", ".jsx", ".json", ".ts", ".tsx"], }, module: { rules: [ { test: /\.(ts|tsx)$/, loader: "awesome-typescript-loader", }, { enforce: "pre", test: /\.js$/, loader: "source-map-loader", }, { test: /\.css$/, loader: "css-loader", }, ], }, plugins: [ new HtmlWebpackPlugin({ template: path.resolve(\__dirname, "src", "components", "index.html"), }), new MiniCssExtractPlugin({ filename: "./src/yourfile.css", }), ], }; Vejamos as diferentes opções que adicionamos ao webpack.config.js :
-
entryIsso especifica o ponto de entrada para nosso aplicativo. Pode ser um único arquivo ou uma matriz de arquivos que desejamos incluir em nossa compilação. -
outputContém a configuração de saída. O aplicativo analisa isso ao tentar enviar código empacotado do nosso projeto para o disco. O caminho representa o diretório de saída para o código a ser enviado e o nome do arquivo representa o nome do arquivo para o mesmo. Geralmente é chamadobundle.js. -
resolveWebpack examina esse atributo para decidir se agrupa ou ignora o arquivo. Assim, em nosso projeto, o webpack considerará arquivos com as extensões.js,.jsx,.json,.tse.tsxpara empacotamento. -
modulePodemos habilitar o webpack para carregar um determinado arquivo quando solicitado pelo aplicativo, usando loaders. É necessário um objeto de regras que especifica que:- qualquer arquivo que termine com a extensão
.tsxou.tsdeve usarawesome-typescript-loaderpara ser carregado; - os arquivos que terminam com a extensão
.jsdevem ser carregados comsource-map-loader; - os arquivos que terminam com a extensão
.cssdevem ser carregados comcss-loader.
- qualquer arquivo que termine com a extensão
-
pluginsO Webpack tem suas próprias limitações e fornece plugins para superá-las e estender suas capacidades. Por exemplo,html-webpack-plugincria um arquivo de modelo que é renderizado para o navegador a partir do arquivoindex.htmlno diretório./src/component/index.html.
MiniCssExtractPlugin renderiza o arquivo CSS pai do aplicativo.
Adicionando scripts ao package.json
Podemos adicionar scripts diferentes para construir aplicativos React em nosso arquivo package.json :
"scripts": { "start": "webpack-dev-server --open", "build": "webpack" }, Agora, execute npm start em sua CLI. Se tudo correu bem, você deve ver isto:

Se você tem um talento especial para webpack, clone o repositório para esta configuração e use-o em seus projetos.
Criando arquivos
Crie uma pasta src e um arquivo index.tsx . Este será o arquivo base que renderiza o React.
Agora, se executarmos npm start , ele executará nosso servidor e abrirá uma nova guia. A execução npm run build compilará o webpack para produção e criará uma pasta de compilação para nós.
Vimos como configurar o TypeScript do zero usando o método de configuração Create React App e webpack.
Uma das maneiras mais rápidas de obter uma compreensão completa do TypeScript é convertendo um de seus projetos React existentes em TypeScript. Infelizmente, a adoção incremental do TypeScript em um projeto vanilla React existente é estressante porque envolve ter que ejetar ou renomear todos os arquivos, o que resultaria em conflitos e um pull request gigante se o projeto pertencesse a uma equipe grande.
Em seguida, veremos como migrar facilmente um projeto React para o TypeScript.
Migrar um aplicativo Create React existente para o TypeScript
Para tornar esse processo mais gerenciável, vamos dividi-lo em etapas, o que nos permitirá migrar em partes individuais. Aqui estão as etapas que seguiremos para migrar nosso projeto:
- Adicione TypeScript e tipos.
- Adicione
tsconfig.json. - Comece pequeno.
- Renomeie a extensão dos arquivos para
.tsx.
1. Adicionar TypeScript ao projeto
Primeiro, precisaremos adicionar o TypeScript ao nosso projeto. Supondo que seu projeto React foi inicializado com Create React App, podemos executar o seguinte:
# Using npm npm install --save typescript @types/node @types/react @types/react-dom @types/jest # Using Yarn yarn add typescript @types/node @types/react @types/react-dom @types/jest Observe que ainda não alteramos nada no TypeScript. Se executarmos o comando para iniciar o projeto localmente ( npm start ou yarn start ), nada muda. Se for esse o caso, então ótimo! Estamos prontos para o próximo passo.
2. Adicione o arquivo tsconfig.json
Antes de aproveitar o TypeScript, precisamos configurá-lo através do arquivo tsconfig.json . A maneira mais simples de começar é criar um scaffold usando este comando:
npx tsc --init Isso nos dá algumas noções básicas, com muito código comentado. Agora, substitua todo o código em tsconfig.json por este:
{ "compilerOptions": { "jsx": "react", "module": "commonjs", "noImplicitAny": true, "outDir": "./build/", "preserveConstEnums": true, "removeComments": true, "sourceMap": true, "target": "es5" }, "include": [ "./src/**/**/\*" ] }Configuração do TypeScript
Vejamos também as diferentes opções que adicionamos ao tsconfig.json :
-
compilerOptionsRepresenta as diferentes opções do compilador.-
targetTraduz construções JavaScript mais recentes para uma versão mais antiga, como ECMAScript 5. -
libAdiciona uma lista de arquivos de biblioteca à compilação (por exemplo, usar es2015 nos permite usar a sintaxe ECMAScript 6). -
jsx:reactAdiciona suporte para JSX em arquivos.tsx. -
libAdiciona uma lista de arquivos de biblioteca à compilação (por exemplo, usar es2015 nos permite usar a sintaxe ECMAScript 6). -
moduleGera o código do módulo. -
noImplicitAnyUsado para gerar erros para declarações comanytipo implícito. -
outDirRepresenta o diretório de saída. -
sourceMapGera um arquivo.map, que pode ser muito útil para depurar nosso aplicativo. -
includeUsado para especificar a lista de arquivos a ser incluída.
-
As opções de configurações variam de acordo com a demanda do projeto. Talvez seja necessário verificar a planilha de opções do TypeScript para descobrir o que se encaixa no seu projeto.
Tomamos apenas as medidas necessárias para deixar as coisas prontas. Nossa próxima etapa é migrar um arquivo para o TypeScript.
3. Comece com um componente simples
Aproveite a capacidade do TypeScript de ser adotado gradualmente. Vá um arquivo de cada vez no seu próprio ritmo. Faça o que faz sentido para você e sua equipe. Não tente resolver tudo de uma vez.
Para converter isso corretamente, precisamos fazer duas coisas:
- Altere a extensão do arquivo para
.tsx. - Adicione a anotação de tipo (o que exigiria algum conhecimento de TypeScript).
4.Renomeie as extensões de arquivo para .tsx
Em uma base de código grande, pode parecer cansativo renomear arquivos individualmente.
Renomeie vários arquivos no macOS
Renomear vários arquivos pode ser uma perda de tempo. Aqui está como você pode fazer isso em um Mac. Clique com o botão direito do mouse (ou Ctrl + clique ou clique com dois dedos simultaneamente no trackpad se estiver usando um MacBook) na pasta que contém os arquivos que deseja renomear. Em seguida, clique em “Revelar no Finder”. No Finder, selecione todos os arquivos que deseja renomear. Clique com o botão direito do mouse nos arquivos selecionados e escolha “Rename X items…” Então, você verá algo assim:

Insira a string que você deseja encontrar e a string com a qual deseja substituir essa string encontrada e clique em “Renomear”. Feito.
Renomeie vários arquivos no Windows
Renomear vários arquivos no Windows está além do escopo deste tutorial, mas um guia completo está disponível. Você normalmente obteria erros após renomear os arquivos; você só precisa adicionar as anotações de tipo. Você pode retocar isso na documentação.
Cobrimos como configurar o TypeScript em um aplicativo React. Agora, vamos construir um aplicativo de seleção de episódios para Money Heist usando TypeScript.
Não abordaremos os tipos básicos de TypeScript. É necessário passar pela documentação antes de continuar neste tutorial.
Hora de construir
Para tornar esse processo menos assustador, dividiremos isso em etapas, o que nos permitirá criar o aplicativo em partes individuais. Aqui estão todos os passos que tomaremos para construir o seletor de episódios do Money Heist :
- Scaffold um aplicativo Create React.
- Buscar episódios.
- Crie os tipos e interfaces apropriados para nossos episódios em
interface.ts. - Configure a loja para buscar episódios em
store.tsx. - Crie a ação para buscar episódios em
action.ts. - Crie um componente
EpisodeList.tsxque contém os episódios buscados. - Importe o componente
EpisodesListpara nossa página inicial usandoReact Lazy and Suspense.
- Crie os tipos e interfaces apropriados para nossos episódios em
- Adicione episódios.
- Configure a loja para adicionar episódios em
store.tsx. - Crie a ação para adicionar episódios em
action.ts.
- Configure a loja para adicionar episódios em
- Remover episódios.
- Configure a loja para excluir episódios em
store.tsx. - Crie a ação para excluir episódios em
action.ts.
- Configure a loja para excluir episódios em
- Episódio favorito.
- Importar o componente
EpisodesListno episódio favorito. - Render
EpisodesListdentro do episódio favorito.
- Importar o componente
- Usando o Reach Router para navegação.
Configurar o React
A maneira mais fácil de configurar o React é usar o Create React App. Create React App é uma maneira oficialmente suportada de criar aplicativos React de página única. Ele oferece uma configuração de compilação moderna sem configuração.
Vamos usá-lo para inicializar o aplicativo que vamos construir. Na sua CLI, execute o comando abaixo:
npx create-react-app react-ts-app && cd react-ts-app Quando a instalação for bem-sucedida, inicie o servidor React executando npm start .

Entendendo interfaces e tipos no Typescript
Interfaces em TypeScript são usadas quando precisamos dar tipos a propriedades de objetos. Portanto, estaríamos usando interfaces para definir nossos tipos.
interface Employee { name: string, role: string salary: number } const bestEmployee: Employee= { name: 'John Doe', role: 'IOS Developer', salary: '$8500' //notice we are using a string } Ao compilar o código acima, veríamos este erro: “Tipos de salary de propriedade são incompatíveis. O tipo string não pode ser atribuído ao tipo number .”
Esses erros ocorrem no TypeScript quando uma propriedade ou variável recebe um tipo diferente do tipo definido. Especificamente, o trecho acima significa que a propriedade de salary foi atribuída a um tipo de string em vez de um tipo de number .
Vamos criar um arquivo interface.ts em nossa pasta src . Copie e cole este código nele:
/** |-------------------------------------------------- | All the interfaces! |-------------------------------------------------- */ export interface IEpisode { airdate: string airstamp: string airtime: string id: number image: { medium: string; original: string } name: string number: number runtime: number season: number summary: string url: string } export interface IState { episodes: Array<IEpisode> favourites: Array<IEpisode> } export interface IAction { type: string payload: Array<IEpisode> | any } export type Dispatch = React.Dispatch<IAction> export type FavAction = ( state: IState, dispatch: Dispatch, episode: IEpisode ) => IAction export interface IEpisodeProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> } export interface IProps { episodes: Array<IEpisode> store: { state: IState; dispatch: Dispatch } toggleFavAction: FavAction favourites: Array<IEpisode> }É uma boa prática adicionar um “I” ao nome da interface. Isso torna o código legível. No entanto, você pode decidir excluí-lo.
Interface de episódio IE
Nossa API retorna um conjunto de propriedades como airdate , airstamp , airtime , id , image , name , number , runtime , season , summary e url . Assim, definimos uma interface IEpisode e configuramos os tipos de dados apropriados para as propriedades do objeto.
I Interface de estado
Nossa interface IState possui propriedades de episodes e favorites , respectivamente, e uma interface Array<IEpisode> .
IAção
As propriedades da interface IAction são payload e type . A propriedade type tem um tipo string, enquanto a carga útil tem um tipo Array | any Array | any .
Observe que Array | any Array | any significa uma matriz da interface do episódio ou qualquer tipo.
O tipo Dispatch é definido como React.Dispatch e uma interface <IAction> . Observe que React.Dispatch é o tipo padrão para a função dispatch , de acordo com a base de código @types/react react, enquanto <IAction> é um array da ação Interface.
Além disso, o Visual Studio Code tem um verificador de TypeScript. Portanto, apenas destacando ou passando o mouse sobre o código, é inteligente o suficiente para sugerir o tipo apropriado.
Em outras palavras, para usarmos nossa interface em nossos aplicativos, precisamos exportá-la. Até agora, temos nossa loja e nossas interfaces que guardam o tipo de nosso objeto. Vamos agora criar nossa loja. Observe que as outras interfaces seguem as mesmas convenções explicadas.

Buscar episódios
Criando uma loja
Para buscar nossos episódios, precisamos de um armazenamento que contenha o estado inicial dos dados e que defina nossa função redutora.
Usaremos o gancho useReducer para configurar isso. Crie um arquivo store.tsx em sua pasta src . Copie e cole o seguinte código nele.
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext (initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
A seguir estão as etapas que tomamos para criar a loja:
- Ao definir nossa loja, precisamos do gancho
useReducere da APIcreateContextdo React, e é por isso que a importamos. - Importamos
IStateeIActionde./types/interfaces. - Declaramos um objeto
initialStatecom um tipo deIStatee propriedades de episódios e favoritos, que são ambos definidos como um array vazio, respectivamente. - Em seguida, criamos uma variável
Storeque contém o métodocreateContexte que recebe oinitialState.
O tipo de método createContext é <IState | any> <IState | any> , o que significa que pode ser um tipo de <IState> ou any . Veremos o tipo any usado com frequência neste artigo.
- Em seguida, declaramos uma função
reducere passamosstateeactioncomo parâmetros. A funçãoreducerpossui uma instrução switch que verifica o valor deaction.type. Se o valor forFETCH_DATA, ele retornará um objeto que possui uma cópia do nosso estado(...state)e do estado do episódio que contém nossa carga útil de ação. - Na instrução switch, retornamos um estado
default.
Observe que os parâmetros de state e action na função redutora têm os tipos IState e IAction , respectivamente. Além disso, a função reducer tem um tipo de IState .
- Por fim, declaramos uma função
StoreProvider. Isso dará a todos os componentes do nosso aplicativo acesso à loja. - Esta função recebe os
childrencomo prop, e dentro da funçãoStorePrivder, declaramos o hookuseReducer. - Desestruturamos
stateedispatch. - Para tornar nossa loja acessível a todos os componentes, passamos um valor de objeto contendo
stateedispatch.
O state que contém nossos episódios e o estado de favoritos será acessível por outros componentes, enquanto o dispatch é uma função que altera o estado.
- Exportaremos
StoreeStoreProvider, para que possam ser usados em nosso aplicativo.
Criar Action.ts
Precisaremos fazer solicitações à API para buscar os episódios que serão mostrados ao usuário. Isso será feito em um arquivo de ação. Crie um arquivo Action.ts e cole o seguinte código:
import { Dispatch } from './interface/interfaces' export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) }Primeiro, precisamos importar nossas interfaces para que possam ser usadas neste arquivo. As seguintes etapas foram realizadas para criar a ação:
- A função
fetchDataActionusa props dedispatchcomo parâmetro. - Como nossa função é assíncrona,
asynceawait. - Criamos uma variável (
URL) que contém nosso endpoint de API. - Temos outra variável chamada
dataque contém a resposta da API. - Em seguida, armazenamos a resposta JSON em
dataJSON, depois de obtermos a resposta no formato JSON chamandodata.json(). - Por fim, retornamos uma função dispatch que possui uma propriedade do
typee uma string deFETCH_DATA. Ele também tem umpayload()._embedded.episodesé a matriz do objeto de episódios do nossoendpoint.
Observe que a função fetchDataAction busca nosso endpoint, converte-o em objetos JSON e retorna a função dispatch, que atualiza o estado declarado anteriormente na Store.
O tipo de despacho exportado é definido como React.Dispatch . Observe que React.Dispatch é o tipo padrão para a função dispatch de acordo com a base de código @types/react react, enquanto <IAction> é um array da Interface Action.
Componente de lista de episódios
Para manter a capacidade de reutilização de nosso aplicativo, manteremos todos os episódios buscados em um arquivo separado e, em seguida, importaremos o arquivo em nosso componente homePage .
Na pasta de components , crie um arquivo EpisodesList.tsx e copie e cole o seguinte código nele:
import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => { const { episodes } = props return episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Season: {episode.season} Number: {episode.number} </div> <button type='button' > Fav </button> </section> </section> ) }) } export default EpisodesList- Importamos
IEpisodeeIPropsdeinterfaces.tsx. - Em seguida, criamos uma função
EpisodesListque recebe props. As props terão um tipo deIProps, enquanto a função terá um tipo deArray<JSX.Element>.
O Visual Studio Code sugere que nosso tipo de função seja escrito como JSX.Element[] .

Enquanto Array<JSX.Element> é igual a JSX.Element[] , Array<JSX.Element> é chamado de identidade genérica. Portanto, o padrão genérico será usado com frequência neste artigo.
- Dentro da função, desestruturamos os
episodesdeprops, que tem oIEpisodecomo tipo.
Leia sobre a identidade genérica, Este conhecimento será necessário à medida que prosseguirmos.
- Devolvemos os adereços dos
episodese mapeamos para retornar algumas tags HTML. - A primeira seção contém a
key, que éepisode.id, e umclassNamedeepisode-box, que será criado posteriormente. Sabemos que nossos episódios têm imagens; daí, a marca de imagem. - A imagem tem um operador ternário que verifica se há um
episode.imageou umepisode.image.medium. Caso contrário, exibimos uma string vazia se nenhuma imagem for encontrada. Além disso, incluímos oepisode.nameem uma div.
Na section , mostramos a temporada a que pertence um episódio e seu número. Temos um botão com o texto Fav . Exportamos o componente EpisodesList para que possamos usá-lo em nosso aplicativo.
Componente da página inicial
Queremos que a página inicial acione a chamada da API e exiba os episódios usando o componente EpisodesList que criamos. Dentro da pasta de components , crie o componente HomePage e copie e cole o seguinte código nele:
import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { fetchDataAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch } } return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage- Importamos
useContext,useEffect,lazyeSuspensedo React. O componente importado do aplicativo é a base sobre a qual todos os outros componentes devem receber o valor da loja. - Também importamos
Store,IEpisodePropseFetchDataActionde seus respectivos arquivos. - Importamos o componente
EpisodesListusando o recursoReact.lazydisponível no React 16.6.
O carregamento lento do React suporta a convenção de divisão de código. Assim, nosso componente EpisodesList é carregado dinamicamente, em vez de ser carregado de uma só vez, melhorando assim o desempenho do nosso aplicativo.
- Desestruturamos o
stateedispatchcomo adereços daStore. - O e comercial (&&) no gancho
useEffectverifica se nosso estado de episódios estáempty(ou igual a 0). Caso contrário, retornamos a funçãofetchDataAction. - Por fim, retornamos o componente
App. Dentro dele, usamos o wrapperSuspensee definimos ofallbackpara um div com o texto deloading. Isso será exibido para o usuário enquanto aguardamos a resposta da API. - O componente
EpisodesListserá montado quando os dados estiverem disponíveis, e os dados que conterão osepisodessão os que disseminamos nele.
Configurar Index.txs
O componente Homepage precisa ser filho do StoreProvider . Teremos que fazer isso no arquivo index . Renomeie index.js para index.tsx e cole o seguinte código:
import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store' import HomePage from './components/HomePage' ReactDOM.render( <StoreProvider> <HomePage /> </StoreProvider>, document.getElementById('root') ) Importamos StoreProvider , HomePage e index.css de seus respectivos arquivos. We wrap the HomePage component in our StoreProvider . This makes it possible for the Homepage component to access the store, as we saw in the previous section.
Percorremos um longo caminho. Let's check what the app looks like, without any CSS.

Create Index.css
Delete the code in the index.css file and replace it with this:
html { font-size: 14px; } body { margin: 0; padding: 0; font-size: 10px; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; } .episode-layout { display: flex; flex-wrap: wrap; min-width: 100vh; } .episode-box { padding: .5rem; } .header { display: flex; justify-content: space-between; background: white; border-bottom: 1px solid black; padding: .5rem; position: sticky; top: 0; }Our app now has a look and feel. Here's how it looks with CSS.

Now we see that our episodes can finally be fetched and displayed, because we've adopted TypeScript all the way. Great, isn't it?
Add Favorite Episodes Feature
Let's add functionality that adds favorite episodes and that links it to a separate page. Let's go back to our Store component and add a few lines of code:
Note that the highlighted code is newly added:
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload }case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return <Store.Provider value={{ state, dispatch }}>{children}</Store.Provider> }
To implement the “Add favorite” feature to our app, the ADD_FAV case is added. It returns an object that holds a copy of our previous state, as well as an array with a copy of the favorite state , with the payload .
We need an action that will be called each time a user clicks on the FAV button. Let's add the highlighted code to index.tx :
import {IAction, IEpisode, Dispatch } from './types/interfaces'export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON._embedded.episodes }) }export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }export const toggleFavAction = (dispatch: any, episode: IEpisode | any): IAction => { let dispatchObj = { type: 'ADD_FAV', payload: episode } return dispatch(dispatchObj) }
We create a toggleFavAction function that takes dispatch and episodes as parameters, and any and IEpisode|any as their respective types, with IAction as our function type. We have an object whose type is ADD_FAV and that has episode as its payload. Lastly, we just return and dispatch the object.
Adicionaremos mais alguns trechos ao EpisodeList.tsx . Copie e cole o código destacado:
import React from 'react' import { IEpisode, IProps } from '../types/interfaces' const EpisodesList = (props: IProps): Array<JSX.Element> => {const { episodes, toggleFavAction, favourites, store } = props const { state, dispatch } = storereturn episodes.map((episode: IEpisode) => { return ( <section key={episode.id} className='episode-box'> <img src={!!episode.image ? episode.image.medium : ''} alt={`Money Heist - ${episode.name}`} /> <div>{episode.name}</div> <section style={{ display: 'flex', justifyContent: 'space-between' }}> <div> Seasion: {episode.season} Number: {episode.number} </div> <button type='button'onClick={() => toggleFavAction(state, dispatch, episode)} > {favourites.find((fav: IEpisode) => fav.id === episode.id) ? 'Unfav' : 'Fav'}</button> </section> </section> ) }) } export default EpisodesList
Incluímos togglefavaction , favorites e store como props e desestruturamos state , um dispatch da store. Para selecionar nosso episódio favorito, incluímos o método toggleFavAction em um evento onClick e passamos os props state , dispatch e episode como argumentos para a função.
Por fim, percorremos o estado favorite para verificar se fav.id (ID favorito) corresponde ao episode.id . Se isso acontecer, alternamos entre o texto Unfav e Fav . Isso ajuda o usuário a saber se ele marcou esse episódio como favorito ou não.
Estamos chegando perto do fim. Mas ainda precisamos de uma página onde os episódios favoritos possam ser vinculados quando o usuário escolher entre os episódios na página inicial.
Se você chegou até aqui, dê um tapinha nas costas.
Componente de página favorita
Na pasta de components , crie um arquivo FavPage.tsx . Copie e cole o seguinte código nele:
import React, { lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces' import { toggleFavAction } from '../Actions' const EpisodesList = lazy<any>(() => import('./EpisodesList')) export default function FavPage(): JSX.Element { const { state, dispatch } = React.useContext(Store) const props: IEpisodeProps = { episodes: state.favourites, store: { state, dispatch }, toggleFavAction, favourites: state.favourites } return ( <App> <Suspense fallback={<div>loading...</div>}> <div className='episode-layout'> <EpisodesList {...props} /> </div> </Suspense> </App> ) } Para criar a lógica por trás da escolha de episódios favoritos, escrevemos um pequeno código. Importamos lazy e Suspense do React. Também importamos Store , IEpisodeProps e toggleFavAction de seus respectivos arquivos.
Importamos nosso componente EpisodesList usando o recurso React.lazy . Por fim, retornamos o componente App . Dentro dele, usamos o wrapper Suspense e definimos um fallback para um div com o texto de carregamento.
Isso funciona de forma semelhante ao componente Homepage . Este componente acessará a loja para obter os episódios favoritos do usuário. Em seguida, a lista de episódios é passada para o componente EpisodesList .
Vamos adicionar mais alguns trechos ao arquivo HomePage.tsx .
Inclua o toggleFavAction de ../Actions . Inclua também o método toggleFavAction como props.
import React, { useContext, useEffect, lazy, Suspense } from 'react' import App from '../App' import { Store } from '../Store' import { IEpisodeProps } from '../types/interfaces'import { fetchDataAction, toggleFavAction } from '../Actions'const EpisodesList = lazy<any>(() => import('./EpisodesList')) const HomePage = (): JSX.Element => { const { state, dispatch } = useContext(Store) useEffect(() => { state.episodes.length === 0 && fetchDataAction(dispatch) }) const props: IEpisodeProps = { episodes: state.episodes, store: { state, dispatch },toggleFavAction, favourites: state.favourites} return ( <App> <Suspense fallback={<div>loading...</div>}> <section className='episode-layout'> <EpisodesList {...props} /> </section> </Suspense> </App> ) } export default HomePage
Nossa FavPage precisa estar vinculada, então precisamos de um link em nosso cabeçalho em App.tsx . Para isso, usamos o Reach Router, uma biblioteca semelhante ao React Router. William Le explica as diferenças entre o Reach Router e o React Router.
Em sua CLI, execute npm install @reach/router @types/reach__router . Estamos instalando a biblioteca do roteador reach-router .
Após a instalação bem-sucedida, importe o Link de @reach/router .
import React, { useContext, Fragment } from 'react' import { Store } from './tsx'import { Link } from '@reach/router'const App = ({ children }: { children: JSX.Element }): JSX.Element => {const { state } = useContext(Store)return ( <Fragment> <header className='header'> <div> <h1>Money Heist</h1> <p>Pick your favourite episode</p> </div><div> <Link to='/'>Home</Link> <Link to='/faves'>Favourite(s): {state.favourites.length}</Link> </div></header> {children} </Fragment> ) } export default App
Desestruturamos a loja de useContext . Por fim, nossa home terá um Link e um caminho para / , enquanto nosso favorito terá um caminho para /faves .
{state.favourites.length} verifica o número de episódios nos estados favoritos e o exibe.
Finalmente, em nosso arquivo index.tsx , importamos os componentes FavPage e HomePage , respectivamente, e os envolvemos no Router .
Copie o código destacado para o código existente:
import React from 'react' import ReactDOM from 'react-dom' import './index.css' import { StoreProvider } from './Store'import { Router, RouteComponentProps } from '@reach/router' import HomePage from './components/HomePage' import FavPage from './components/FavPage' const RouterPage = ( props: { pageComponent: JSX.Element } & RouteComponentProps ) => props.pageComponentReactDOM.render( <StoreProvider><Router> <RouterPage pageComponent={<HomePage />} path='/' /> <RouterPage pageComponent={<FavPage />} path='/faves' /> </Router></StoreProvider>, document.getElementById('root') )
Agora, vamos ver como funciona o ADD_FAV implementado.

Remover funcionalidade favorita
Por fim, adicionaremos o recurso “Remover episódio”, para que, ao clicar no botão, alternemos entre adicionar ou remover um episódio favorito. Vamos exibir o número de episódios adicionados ou removidos no cabeçalho.
ARMAZENAR
Para criar a funcionalidade “Remover episódio favorito”, adicionaremos outro caso em nossa loja. Então, vá até Store.tsx e adicione o código destacado:
import React, { useReducer, createContext } from 'react' import { IState, IAction } from './types/interfaces' const initialState: IState = { episodes: [], favourites: [] } export const Store = createContext<IState | any>(initialState) const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'FETCH_DATA': return { ...state, episodes: action.payload } case 'ADD_FAV': return { ...state, favourites: [...state.favourites, action.payload] }case 'REMOVE_FAV': return { ...state, favourites: action.payload }default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }default: return state } } export const StoreProvider = ({ children }: JSX.ElementChildrenAttribute): JSX.Element => { const [state, dispatch] = useReducer(reducer, initialState) return {children} }
Adicionamos mais um caso chamado REMOVE_FAV e retornamos um objeto contendo a cópia de nosso initialState . Além disso, o estado de favorites contém a carga útil da ação.
AÇAO
Copie o seguinte código destacado e cole-o em action.ts :
import{ IAction, IEpisode, IState, Dispatch } from './types/interfaces'export const fetchDataAction = async (dispatch: Dispatch) => { const URL = 'https://api.tvmaze.com/singlesearch/shows?q=la-casa-de-papel&embed=episodes' const data = await fetch(URL) const dataJSON = await data.json() return dispatch({ type: 'FETCH_DATA', payload: dataJSON.\_embedded.episodes }) } //Add IState withits typeexport const toggleFavAction = (state: IState, dispatch: any, episode: IEpisode | any): IAction => { const episodeInFav = state.favourites.includes(episode)let dispatchObj = { type: 'ADD_FAV', payload: episode }if (episodeInFav) { const favWithoutEpisode = state.favourites.filter( (fav: IEpisode) => fav.id !== episode.id ) dispatchObj = { type: 'REMOVE_FAV', payload: favWithoutEpisode }} return dispatch(dispatchObj) }
Importamos a interface IState de ./types/interfaces , porque precisaremos passá-la como o tipo para as props de state na função toggleFavAction .
Uma variável episodeInFav é criada para verificar se há um episódio que existe no estado de favorites .
Filtramos o estado de favoritos para verificar se um ID de favorito não é igual a um ID de episódio. Assim, o dispatchObj é reatribuído a um tipo de REMOVE_FAV e uma carga útil de favWithoutEpisode .
Vamos visualizar o resultado do nosso aplicativo.
Conclusão
Neste artigo, vimos como configurar o TypeScript em um projeto React e como migrar um projeto do vanilla React para o TypeScript.
Também criamos um aplicativo com TypeScript e React para ver como o TypeScript é usado em projetos React. Eu acredito que você foi capaz de aprender algumas coisas.
Por favor, compartilhe seus comentários e experiências com o TypeScript na seção de comentários abaixo. Eu adoraria ver o que você inventa!
O repositório de suporte para este artigo está disponível no GitHub.
Referências
- “Como migrar um aplicativo React para o TypeScript”, Joe Previte
- “Por que e como usar o TypeScript em seu aplicativo React?”, Mahesh Haldar
