Melhores Práticas na Programação em TypeScript
Este artigo explica as melhores práticas na programação em TypeScript.
Este guia explica práticas recomendadas para aproveitar os tipos do TypeScript, reduzindo bugs e escrevendo códigos mais legíveis.
YouTube Video
Melhores Práticas na Programação em TypeScript
O maior benefício do TypeScript é 'prevenir bugs com tipos e tornar clara a intenção do código'.
As melhores práticas não são apenas regras, mas um conjunto de princípios para escrever código seguro, legível e fácil de manter. A seguir, apresentamos as melhores práticas mais comuns do TypeScript com exemplos práticos.
Evite any e sempre defina tipos de forma significativa
Primeiro, vamos analisar o ponto de 'evitar o uso de any e atribuir tipos significativos.'.
O uso de any desativa totalmente a verificação de tipos, o que vai contra o propósito de usar TypeScript. Em vez de usar any apenas para fazer funcionar, é importante fornecer tipos tão descritivos quanto possível.
1// Bad
2function parse(data: any) {
3 return data.value;
4}Esse código pode aceitar qualquer valor, então não é possível evitar erros em tempo de execução.
1// Good
2type ParsedData = {
3 value: string;
4};
5
6function parse(data: ParsedData): string {
7 return data.value;
8}Ao definir tipos, você esclarece a intenção de entradas e saídas e aumenta a segurança.
Sempre defina explicitamente a estrutura dos objetos usando type ou interface.
Em seguida, vamos analisar o ponto de 'definir sempre explicitamente as estruturas dos objetos usando type ou interface.'.
Se você usar objetos de forma ad-hoc, sua estrutura pode se tornar ambígua. Sempre extraia tipos para reutilização e manutenção.
1// Bad
2function createUser(user: { name: string; age: number }) {
3 console.log(user.name);
4}Mesmo em pequenos trechos de código, é importante desenvolver o hábito de separar os tipos.
1// Good
2type User = {
3 name: string;
4 age: number;
5};
6
7function createUser(user: User): void {
8 console.log(user.name);
9}Ao nomear os tipos, entender toda a base de código fica muito mais fácil.
Use tipos união para representar de forma precisa todos os estados possíveis.
Se você usar tipos string ou number puros em condicionais, valores inesperados podem passar despercebidos. Ao utilizar tipos de união, você pode representar apenas os estados permitidos no nível do tipo.
1// Bad
2function setStatus(status: string) {
3 console.log(status);
4}Neste código, cadeias de caracteres sem sentido ou valores incorretos não podem ser detectados em tempo de compilação.
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4function setStatus(status: Status): void {
5 console.log(status);
6}Ao utilizar tipos de união, você pode eliminar de forma confiável ‘estados impossíveis’ em tempo de compilação. Como resultado, a segurança dos ramos condicionais e a confiabilidade do código irão melhorar.
Trate null e undefined de forma explícita.
Depois, vamos analisar o ponto de 'lidar explicitamente com null e undefined.'.
No TypeScript, é importante expressar a possível ausência de um valor no tipo. Se você mantiver ambiguidades, isso pode levar a erros em tempo de execução.
1type User = {
2 name: string;
3 email?: string;
4};email pode não existir, então você deve tratá-lo considerando essa possibilidade.
1function printEmail(user: User): void {
2 if (user.email) {
3 console.log(user.email);
4 }
5}Sempre verifique valores opcionais antes de usá-los.
Use afirmações de tipo (as) apenas como última opção.
Agora, vamos analisar o ponto de 'não usar excessivamente as asserções de tipo.'.
As asserções de tipo ignoram temporariamente a verificação de tipos do TypeScript para declarar: 'Eu sei que este valor é deste tipo.'. O uso excessivo delas prejudica a segurança dos tipos.
1// Bad
2const value = input as string;Neste código, mesmo que o valor real não seja uma string, nenhum erro acontece, o que pode causar erros de execução. Como mostrado no código a seguir, escolha verificar de forma segura usando type guards primeiro.
1// Good
2function isString(value: unknown): value is string {
3 return typeof value === "string";
4}
5
6if (isString(input)) {
7 console.log(input.toUpperCase());
8}Type guards são um mecanismo para determinar com segurança um tipo durante a verificação do valor real. Ao priorizar os type guards em vez das asserções de tipo, você pode evitar erros de execução com mais facilidade.
Não dependa muito da inferência de tipos para os tipos de retorno.
Depois, vamos analisar o ponto de 'não depender demais da inferência para os tipos de retorno.'.
A inferência de tipos do TypeScript é poderosa, mas é mais seguro anotar explicitamente os tipos de retorno de funções públicas. Isso minimiza o impacto potencial de mudanças futuras.
1// Bad
2function sum(a: number, b: number) {
3 return a + b;
4}Deixe clara sua intenção mesmo em funções pequenas.
1// Good
2function sum(a: number, b: number): number {
3 return a + b;
4}Declarar os tipos de retorno aumenta a estabilidade da sua API.
Trate entradas com segurança utilizando unknown.
Agora, vamos analisar o ponto de 'aceitar entradas externas com segurança usando unknown.'.
Para entrada externa como APIs, JSON ou entrada do usuário, use unknown em vez de any. Ao fazer isso, você garante que todos os valores sejam validados, mantendo a segurança de tipos.
1// Bad
2function handleResponse(data: any) {
3 console.log(data.id);
4}Veja como validar tipos utilizando unknown.
1// Good
2function handleResponse(data: unknown): void {
3 if (
4 typeof data === "object" &&
5 data !== null &&
6 "id" in data
7 ) {
8 console.log((data as { id: number }).id);
9 }
10}unknown não pode ser utilizado como está; é um tipo que requer validação. É especialmente eficaz ao lidar com entradas externas.
Aumente a expressividade combinando tipos menores.
Em seguida, vamos analisar o ponto de 'aumentar a expressividade combinando tipos pequenos.'.
Definir tipos grandes de uma vez só reduz a legibilidade e manutenibilidade. Divida seus tipos em unidades significativas e combine-os quando necessário.
1type Id = number;
2
3type UserProfile = {
4 id: Id;
5 name: string;
6};
7
8type UserWithStatus = UserProfile & {
9 status: "active" | "inactive";
10};Tratar tipos como componentes ajuda a organizar seu design.
type e interface
Vantagens de interface
type e interface podem ambos definir tipos, mas seus usos e características diferem. Ao utilizá-los para os papéis apropriados, a intenção das suas definições de tipo se torna mais clara.
1// Bad
2type User = {
3 id: number;
4 name: string;
5};
6
7type AdminUser = {
8 id: number;
9 name: string;
10 role: "admin";
11};Se você duplicar partes comuns assim, seu código se torna frágil a mudanças.
1// Good
2interface User {
3 id: number;
4 name: string;
5}
6
7interface AdminUser extends User {
8 role: "admin";
9}interface é ideal para projetos que envolvem extensão (extends) e é mais adequada para expressar o 'formato' de objetos.
Vantagens de type
Por outro lado, type é mais expressivo e adequado para trabalhar com tipos união e interseção.
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4type ApiResponse<T> =
5 | { status: "success"; data: T }
6 | { status: "error"; message: string };type é bastante adequado para expressar estados, opções e combinações.
Diretrizes para escolher entre type e interface
Como regra geral, use interface para estruturas e contratos de objetos, e type quando precisar da expressividade de união, interseção ou operações de tipo.
Ambos funcionam de forma semelhante, mas é importante escolher de acordo com o que comunica melhor a razão da existência do tipo.
Trate seus tipos como documentação.
Por fim, vamos analisar o ponto de 'escrever os tipos como documentação.'.
Boas definições de tipo transmitem mais informações do que comentários poderiam. É importante buscar um estado em que 'a especificação possa ser compreendida apenas olhando para os tipos.'.
1type ApiError = {
2 code: number;
3 message: string;
4 retryable: boolean;
5};Dessa forma, uma das principais vantagens do TypeScript é que as definições de tipo podem funcionar como uma espécie de documento de especificação.
Resumo
As melhores práticas no TypeScript não têm a ver com ser excessivamente rigoroso. A essência é esclarecer a intenção através dos tipos e escrever código robusto a mudanças.
Acumulando pequenas regras no desenvolvimento diário, você pode conseguir efeitos de longo prazo como 'reviews mais fáceis', 'menos bugs' e 'melhor entendimento para você mesmo e para os outros no futuro.'.
Primeiro, pensando em 'como posso expressar isso com tipos?', você escreverá um código de alta qualidade, no estilo TypeScript.
Você pode acompanhar o artigo acima usando o Visual Studio Code em nosso canal do YouTube. Por favor, confira também o canal do YouTube.