Utilizando Typescript
TypeScript é uma forma popular de adicionar definições de tipos à bases de códigos JavaScript. Por padrão, o TypeScript oferece suporte ao JSX e permite que você tenha total suporte para o React Web adicionando @types/react
e @types/react-dom
ao seu projeto.
Você aprenderá
Instalação
Todos os frameworks React em produção oferecem suporte para o uso de TypeScript. Siga o guia específico do framework para instalação:
Adicionando TypeScript a um projeto React existente
Para instalar a versão mais recente das definições de tipos do React:
As seguintes opções do compilador precisam ser definidas em seu tsconfig.json
:
dom
precisa ser incluído emlib
(Nota: se nenhuma opção delib
for especificada,dom
será incluido por padrão).jsx
deve ser definido como uma das opções válidas.preserve
deve ser suficiente para a maioria das aplicações. Se você está publicando uma biblioteca, consulte a documentação dojsx
sobre qual valor escolher.
TypeScript com Componentes React
Escrever TypeScript com React é muito parecido com escrever JavaScript com React. A principal diferença ao trabalhar com um componente é que você pode especificar tipos para as props do seu componente. Estes tipos podem ser usados para checar sua exatidão e prover documentação incorporada em editores.
Utilizando o componente MyButton
do Guia de Início rápido, podemos adicionar um tipo descrevendo o title
para o botão.
function MyButton({ title }: { title: string }) { return ( <button>{title}</button> ); } export default function MyApp() { return ( <div> <h1>Bem-vindo ao my app</h1> <MyButton title="Eu sou um botão" /> </div> ); }
Esta sintaxe em uma mesma linha é a forma mais simples de fornecer tipos para um componente, no entanto à medida que se tem alguns campos a mais para serem descritos, as coisas podem ficar complicadas. Ao invés disso, você pode usar uma interface
ou um type
para descrever as props de um componente:
interface MyButtonProps { /** O texto à ser exibido dentro do botão */ title: string; /** Se poderá haver interação com o botão */ disabled: boolean; } function MyButton({ title, disabled }: MyButtonProps) { return ( <button disabled={disabled}>{title}</button> ); } export default function MyApp() { return ( <div> <h1>Bem-vindo ao my app</h1> <MyButton title="Eu sou um botão desabilitado" disabled={true} /> </div> ); }
O tipo que descreve as props do seu componente pode ser mais simples ou mais complexo conforme sua necessidade, embora deva ser um tipo objeto descrito seja como type
ou interface
. Você pode aprender mais sobre como o TypeScript descreve objetos em Object Types assim como você pode se interessar no uso de Union Types para descrever uma prop que possa ser uma entre alguns diferentes tipos e o guia Creating Types from Types para casos de uso mais avançados.
Exemplos de Hooks
As definições de tipos em @types/react
incluem tipos para os hooks nativos, então você pode usá-los em seus componentes sem nenhuma configuração adicional. Eles são construídos levando em conta o código que você escreve em seu componente, então você terá a inferência de tipo na maior parte do tempo e idealmente você não precisará se preocupar com as minúcias de fornecer tipos.
Entretanto, podemos observar alguns exemplos de como fornecer tipos para hooks.
useState
O hook useState
irá reutilizar o valor passado como state inicial para determinar qual o tipo o valor deve ser. Por exemplo:
// Tipo inferido como "boolean"
const [enabled, setEnabled] = useState(false);
Isso atribuirá o tipo boolean
a enabled
, e setEnabled
será uma função que aceita um argumento boolean
ou uma função que retorna um boolean
. Se você quiser fornecer explicitamente um tipo para o estado, você pode fazê-lo fornecendo um argumento de tipo para a chamada useState
:
// Explicitamente define o tipo como "boolean"
const [enabled, setEnabled] = useState<boolean>(false);
Não é algo muito útil neste caso, mas um caso comum seria onde você deseja informar um tipo que representa um union type. Por exemplo, status
aqui pode ser uma dentre algumas strings:
type Status = "ocioso" | "carregando" | "sucesso" | "erro";
const [status, setStatus] = useState<Status>("ocioso");
Ou, como recomendado em Princípios para estruturar estados, você pode agrupar states relacionados em um objeto descrevendo suas diferentes possibilidades através de tipos objetos:
type RequestState =
| { status: 'ocioso' }
| { status: 'carregando' }
| { status: 'sucesso', data: any }
| { status: 'erro', error: Error };
const [requestState, setRequestState] = useState<RequestState>({ status: 'ocioso' });
useReducer
O hook useReducer
é um hook mais complexo que recebe uma função reducer e um state inicial. Os tipos para a função reducer são inferidos a partir do state inicial. Você pode opcionalmente informar um tipo como argumendo para a chamada de useReducer
para informar um tipo para o state, mas é frequentemente melhor definir o tipo no state inicial:
import {useReducer} from 'react'; interface State { count: number }; type CounterAction = | { type: "reset" } | { type: "setCount"; value: State["count"] } const initialState: State = { count: 0 }; function stateReducer(state: State, action: CounterAction): State { switch (action.type) { case "reset": return initialState; case "setCount": return { ...state, count: action.value }; default: throw new Error("Ação desconhecida"); } } export default function App() { const [state, dispatch] = useReducer(stateReducer, initialState); const addFive = () => dispatch({ type: "setCount", value: state.count + 5 }); const reset = () => dispatch({ type: "reset" }); return ( <div> <h1>Bem vindo ao meu contador</h1> <p>Contador: {state.count}</p> <button onClick={addFive}>Adicione 5</button> <button onClick={reset}>Resetar</button> </div> ); }
Estamos usando TypeScript em alguns lugares importantes:
interface State
descreve a forma do state do reducer.type CounterAction
descreve as diferentes actions das quais podem ser executadas no reducer.const initialState: State
informa um tipo para o state inicial assim como o tipo usado pelouseReducer
por padrão.stateReducer(state: State, action: CounterAction): State
define os tipos para os argumentos da função reducer e o valor do retorno.
Uma alternativa mais explícita para definir o tipo de initialState
é informar um tipo como argumento para o useReducer
:
import { stateReducer, State } from './your-reducer-implementation';
const initialState = { count: 0 };
export default function App() {
const [state, dispatch] = useReducer<State>(stateReducer, initialState);
}
useContext
o hook useContext
utiliza uma técnica de transmissão de dados pela árvore de componentes sem a necessidade de passar props entre componentes. É utilizado criando um componente provider e um hook que consome o valor em um componente filho.
O tipo do valor informado pelo contexto é inferido a partir do valor passado durante a chamada da função createContext
:
import { createContext, useContext, useState } from 'react'; type Theme = "claro" | "escuro" | "sistema"; const ThemeContext = createContext<Theme>("sistema"); const useGetTheme = () => useContext(ThemeContext); export default function MyApp() { const [theme, setTheme] = useState<Theme>('claro'); return ( <ThemeContext.Provider value={theme}> <MyComponent /> </ThemeContext.Provider> ) } function MyComponent() { const theme = useGetTheme(); return ( <div> <p>Tema atual: {theme}</p> </div> ) }
Essa técnica funciona quando você tem um valor padrão que faz sentido, mas há casos onde isso não acontece, e nesses casos utilizar null
como um valor padrão pode parecer razoável. Porém, para permitir que o sistema de tipos compreenda o seu código, você precisa explicitamente definir ContextShape | null
na chamada de createContext
.
Isso causa um problema onde é necessário eliminar o | null
no tipo onde se consome o contexto. A recomendação é fazer com que o hook faça uma checagem de sua existência em tempo de execução e cause um erro caso não exista:
import { createContext, useContext, useState, useMemo } from 'react';
// Este é um exemplo mais simples, mas você pode imaginar um objeto mais complexo aqui
type ComplexObject = {
kind: string
};
// O contexto foi criado com `| null` no tipo, para refletir precisamente o valor padrão.
const Context = createContext<ComplexObject | null>(null);
// O `| null` será removido via checagem pelo hook
const useGetComplexObject = () => {
const object = useContext(Context);
if (!object) { throw new Error("useGetComplexObject deve ser usado dentro de um Provider") }
return object;
}
export default function MyApp() {
const object = useMemo(() => ({ kind: "complexo" }), []);
return (
<Context.Provider value={object}>
<MyComponent />
</Context.Provider>
)
}
function MyComponent() {
const object = useGetComplexObject();
return (
<div>
<p>Objeto atual: {object.kind}</p>
</div>
)
}
useMemo
O hook useMemo
cria/reacessa um valor memoizado de uma chamada de função, executando-a novamente apenas quando as dependências passadas como segundo parâmetro mudarem. O resultado da chamada do hook é inferido pelo valor retornado pela função do primeiro parâmetro. Você pode ser mais explícito informando um tipo como argumento para o hook.
// O tipo de visibleTodos é inferido pelo retorno do valor de filterTodos
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
useCallback
O useCallback
fornece uma referência estável à uma função, desde que as dependências passadas no segundo parâmetro sejam as mesmas. Assim como o useMemo
, o tipo da função é inferido a partir do valor de retorno da função do primeiro parâmetro, e você pode ser mais explícito fornecendo um tipo como argumento ao hook.
const handleClick = useCallback(() => {
// ...
}, [todos]);
Ao trabalhar no strict mode do TypeScript, o useCallback
requer a adição de tipos para os parâmetros da callback. Isso ocorre porque o tipo da callback é inferido a partir do valor de retorno da função e, sem parâmetros, o tipo não pode ser totalmente compreendido.
Dependendo de suas preferências de estilo de código, você pode usar as funções *EventHandler
dos tipos do React para fornecer o tipo do manipulador de eventos ao mesmo tempo em que define a callback:
import { useState, useCallback } from 'react';
export default function Form() {
const [value, setValue] = useState("Mude-me");
const handleChange = useCallback<React.ChangeEventHandler<HTMLInputElement>>((event) => {
setValue(event.currentTarget.value);
}, [setValue])
return (
<>
<input value={value} onChange={handleChange} />
<p>Valor: {value}</p>
</>
);
}
Tipos Úteis
Há um conjunto bastante amplo de tipos provenientes do pacote @types/react
, que vale a pena ler quando você se sentir confortável sobre como o React e o TypeScript interagem. Você pode encontrá-los [na pasta do React em DefinitelyTyped] (https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/react/index.d.ts). Abordaremos alguns dos tipos mais comuns aqui.
Eventos do DOM
Ao trabalhar com eventos do DOM no React, o tipo do evento geralmente pode ser inferido a partir do manipulador de eventos. No entanto, quando você quiser extrair uma função passada à um manipulador de eventos, será necessário definir explicitamente o tipo do evento.
import { useState } from 'react'; export default function Form() { const [value, setValue] = useState("Mude-me"); function handleChange(event: React.ChangeEvent<HTMLInputElement>) { setValue(event.currentTarget.value); } return ( <> <input value={value} onChange={handleChange} /> <p>Valor: {value}</p> </> ); }
Há muitos tipos de eventos fornecidos nos tipos do React - a lista completa pode ser encontrada aqui, que se baseia nos eventos mais populares do DOM.
Para determinar o tipo que está procurando, você pode examinar as informações do manipulador de eventos utilizado colocando o ponteiro do mouse sobre ele, que mostrará o tipo do evento
Se precisar usar um evento que não esteja incluído nessa lista, você poderá usar o tipo React.SyntheticEvent
, que é o tipo base para todos os eventos.
Children
Há dois meios comuns para descrever o children de um componente. O primeiro é usar o tipo React.ReactNode
, que é uma união de todos os tipos possíveis que podem ser passados como filhos no JSX:
interface ModalRendererProps {
title: string;
children: React.ReactNode;
}
Essa é uma definição bem ampla de children. A segunda é usar o tipo React.ReactElement
, que corresponde apenas a elementos JSX e não tipos primitivos do JavaScript, como strings ou numbers:
interface ModalRendererProps {
title: string;
children: React.ReactElement;
}
Note que você não pode usar o TypeScript para descrever que os filhos são de um determinado tipo de elemento JSX, portanto, não é possível usar o sistema de tipos para descrever um componente que só aceita filhos <li>
.
Você pode ver todos os exemplos de ambos React.ReactNode
e React.ReactElement
com checagem de tipos com este playground do TypeScript.
Props de Estilo
Ao usar estilos inline no React, você pode usar React.CSSProperties
para descrever o objeto passado para a prop style
. Esse tipo é uma união de todas as propriedades CSS possíveis e é uma boa forma de garantir que você esteja passando propriedades CSS válidas para a prop style
e de obter o preenchimento automático em seu editor.
interface MyComponentProps {
style: React.CSSProperties;
}
Conteúdo adicional
Este guia abordou os conceitos básicos do uso do TypeScript com React, mas há muito mais para aprender. As páginas individuais de cada API na documentação podem conter uma informação mais detalhada sobre como usá-las com o TypeScript.
Recomendamos as seguintes fontes
-
The TypeScript handbook é a documentação oficial do TypeScript e abrange a maioria dos principais recursos da linguagem.
-
The TypeScript release notes aborda detalhadamente cada um dos novos recursos.
-
React TypeScript Cheatsheet é um guia mantido pela comunidade para o uso de TypeScript com React, cobrindo um monte de casos específicos e oferecendo mais aprofundamento do que este documento.
-
TypeScript Community Discord é um ótimo lugar para fazer perguntas e obter ajuda com problemas de TypeScript e React.