Dando continuidade ao artigo anterior - Tipos básicos do TypeScript - Parte 1 (um ano depois! rsrs) - vamos explorar type aliases, interfaces, literal types e outros conceitos para fortalecer ainda mais sua base em TypeScript!
Tabela de conteúdos
- Type aliases
- Interfaces
- Type assertions
- Literal types
- Null e undefined
- Enums
- Primitivos menos comuns
- Conclusão
Type aliases
Lá atrás, no nosso primeiro artigo, usávamos os object types (tipos de objeto) e union types da seguinte maneira:
function showUser(user: { name: string, surname: string }) {
console.log(`Nome: ${user.name}`);
console.log(`Sobrenome: ${user.surname}`);
}
showUser({ name: 'Marcelo', surname: 'Sarinho' });
Nesse caso, não tem nenhum problema. Mas e se você precisasse escrever o tipo do parâmetro user
várias vezes? Isso seria muito repetitivo e estressante (e ninguém merece digitar tipos enormes toda hora, né?). Para isso, temos os type aliases: um nome para os tipos!
Portanto, o código anterior poderia ser reescrito da seguinte maneira:
type User = {
name: string;
surname: string;
}
function showUser(user: User) {
console.log(`Nome: ${user.name}`);
console.log(`Sobrenome: ${user.surname}`);
}
showUser({ name: 'Marcelo', surname: 'Sarinho' });
Podemos usar o type alias para qualquer tipo, inclusive union types:
type Age = string | number;
💡 Dica: É importante notar que os type aliases são só nomes! Não conseguimos utilizá-los para criar versões diferentes do mesmo tipo! Eles servem como uma versão "abreviada" dos tipos!
Nesse exemplo, o código está correto (apesar de parecer estranho), porque o tipo Name
nada mais é do que um tipo string
apelidado de Name
!
type Name = string;
function greeting(name: Name): string {
return `Olá ${name}!`;
}
greeting('Marcelo');
Interfaces
É uma outra maneira de apelidarmos tipos.
Seguindo o exemplo anterior, temos:
interface User {
name: string;
surname: string;
}
function showUser(user: User) {
console.log(`Nome: ${user.name}`);
console.log(`Sobrenome: ${user.surname}`);
}
showUser({ name: 'Marcelo', surname: 'Sarinho' });
Já que o TypeScript é uma linguagem de programação estruturalmente tipada, ele permite essa situação sem problemas. Ele só está preocupado com a estrutura do valor que passamos e se tem as propriedades esperadas!
Mas vale notar que há diferenças entre Interfaces e Type aliases, apesar de serem extremamente similares.
Diferença entre Interfaces e Type aliases
Na grande parte dos casos você pode escolher o que deseja, já que a maioria das funcionalidades da interface
está disponível no type
.
A diferença está em:
- uma
interface
pode ser estendida diretamente, notype
é necessário usar&
para estendê-lo. - uma
interface
pode ser reaberta para adicionar novas propriedades,type
não.
Extender
// Interface
interface Vehicle {
color: string;
}
interface Car extends Vehicle {
name: string;
}
const car = getCar();
car.color;
car.name;
// -----------------------------------------------------------
// Type
type Vehicle = {
color: string;
}
type Car = Vehicle & {
name: string
}
const car = getCar();
car.color;
car.name;
Reabrir
// Interface
interface Movie {
name: string;
}
interface Movie {
genre: string;
}
// -------------------------------------------------------------
// Type
type Movie = {
name: string;
}
type Movie = {
genre: string
}
// Gera o erro Error: Duplicate identifier 'Window'.
Novamente, na maior parte dos casos, use o que preferir! Futuramente, veremos mais a fundo esses conceitos.
Type assertions
Há casos em que o TypeScript não tem informação sobre o tipo de um valor, enquanto nós temos essa informação, com absoluta certeza.
Por exemplo: estamos usando document.getElementById
para pegar o elemento de ID card
. Nessa situação, o TypeScript sabe que será retornado um tipo HTMLElement
, mas não sabe mais do que isso. Entretanto, nós sabemos que o elemento de ID card
é uma div
! Podemos então usar um type assertion:
const card = document.getElementById('card') as HTMLDivElement;
💡 Dica: O TypeScript só permite o uso de type assertions entre tipos compatíveis (mais ou menos específicos), nunca entre tipos totalmente diferentes! Não é possível "trocar" o tipo para outro abruptamente (string para number, number para string, etc)! Exemplo:
const year = 2025 as string
// Isso daqui geraria um erro!
Literal types
Além dos tipos que vimos anteriormente (strings, numbers, objects, etc), podemos ter strings ou números específicos. Um literal type (ou tipo literal) é um valor exato, não apenas o tipo geral. Exemplo:
const text = "Texto";
// Nesse caso, o tipo da variável text será "Texto"
Uma boa maneira de lembrar do conceito de literal types é comparar com as variáveis JavaScript:
-
let
evar
permitem alterar o conteúdo da variável, sendo assim, seu tipo seria mais geral, umastring
, por exemplo. -
const
não permite alterar o conteúdo da variável, sendo assim, o tipo seria específico, como"Teste"
, por exemplo.
let var1 = "Variável um";
// Tipo: string
const var2 = "Variável dois";
// Tipo: "Variável dois";
Sozinho, um literal type não é muito útil... Mas, se combinados (através de um union), eles são muito úteis!
Vamos supor que temos uma função que mostra o cargo de um programador, e esses cargos só podem ser júnior, pleno ou sênior. Teríamos:
function showRole(role: 'junior' | 'pleno' | 'senior') {
console.log(role);
}
showRole('junior'); // Correto!
showRole('tech lead');
// Aqui, temos um erro! Não é possível passar o argumento "tech lead", pois só aceita 'junior', 'pleno' ou 'senior'.
Além de strings, os literal types também funcionam com números ou tipos não literais também!
Literal inference
Ao lidarmos com objetos, a situação é um pouco diferente. Quando inicializamos uma variável com um objeto, o TypeScript não entende que aquilo é um literal type, pois acha que aquela propriedade pode mudar de valor depois.
Esse comportamento é chamado de literal inference. Quando inicializamos objetos, o TypeScript infere tipos mais genéricos, já que as propriedades podem mudar de valor.
Exemplo:
const obj = { likes: 0 };
if (condition) {
obj.likes = 1; // Aqui, a propriedade mudou de valor
}
O TypeScript entende que a propriedade likes
é do tipo number
, e não 0
. Por causa disso, esse exemplo não emite nenhum erro. O mesmo é aplicado a strings
.
O que fazer nesse caso? Temos duas maneiras de "passar por cima" disso. Observe o exemplo a seguir:
declare function addRole(name: string, role: "ADMIN" | "SUPER" | "COMMON"): void;
const employee = { name: "Marcelo", role: "SUPER" };
addRole(employee.name, employee.role);
// Aqui, temos um erro: Argument of type 'string' is not assignable to parameter of type '"ADMIN" | "SUPER" | "COMMON"'.
Percebeu que o TypeScript não entendeu a propriedade role
como SUPER
, mas sim como uma string
? Então, foi isso que ocasionou o erro! Para corrigirmos, teríamos duas opções:
Usando type assertion
Utilizamos um type assertion para transformar employee.role
em SUPER
.
const employee = { name: "Marcelo", role: "SUPER" as "SUPER" };
// ou também
addRole(employee.name, employee.role as "SUPER");
Usando as const
Utilizamos as const
para converter o objeto e transformá-lo em um literal type.
const employee = { name: "Marcelo", role: "SUPER" } as const;
addRole(employee.name, employee.role);
Pronto, resolvido!
Null e undefined
O TypeScript tem os tipos null
e undefined
, e o comportamento deles depende da maneira que o seu strictNullChecks
está configurado no arquivo tsconfig.json
.
Com strictNullChecks desligado
Nesse caso, valores que podem ser null
ou undefined
podem ser acessados sem problema, e também é possível atribuir esses valores para uma propriedade. Entretanto, essa configuração pode gerar MUITOS bugs, portanto, é recomendado que utilize o strictNullChecks
!
Com strictNullChecks ligado
Esse é o caso "ideal". Nessa situação, precisamos verificar o valor antes de usar seus métodos ou propriedades. Exemplo:
function upperCaseString(text: string | null) {
if (text) {
console.log(text.toUpperCase());
} else {
console.log("Não há texto!");
}
}
// Nesse caso, teria um erro
function upperCaseStringAgain(anotherText: string | null) {
console.log(anotherText.toUpperCase());
// Aqui, temos um aviso: 'anotherText' is possibly 'null'.
}
Aqui, só conseguimos utilizar o .toUpperCase()
se o valor for diferente de null
.
Temos uma outra maneira de remover o null
ou undefined
de um tipo: usando o non-null assertion operator (!). Esse operador garante que o valor não é null
nem undefined
.
function upperCaseString(text: string | null) {
console.log(text!.toUpperCase());
}
💡 Dica: Tome cuidado! Só use esse operador caso tenha CERTEZA de que o valor não é null
nem undefined
, se não podem surgir bugs inesperados na sua aplicação!
Enums
Permite que o programador defina um conjunto de constantes usando nomes. Exemplo:
enum Color {
Red = 1,
Yellow,
Blue,
}
Enums são uma funcionalidade adicionada pelo TypeScript (não existem no JavaScript puro) e vão além do sistema de tipos. Por causa disso, recomendo ler a documentação oficial do TypeScript sobre Enums.
Primitivos menos comuns
Esses são primitivos que não aparecem tão frequentemente, mas vale a pena saber sobre sua existência para você não ficar confuso caso apareçam nos códigos.
bigint
Primitivo utilizado para inteiros muito grandes:
const hundred: bigint = BigInt(100);
Para se aprofundar nesse tipo, acesse a documentação oficial do TypeScript.
symbol
Primitivo utilizado para criar uma referência global única:
const oneName = Symbol("name");
const anotherName = Symbol("name");
if (oneName === anotherName) {
// ...
}
// Nesse caso, temos um erro: This comparison appears to be unintentional because the types 'typeof oneName' and 'typeof anotherName' have no overlap.
Para se aprofundar nesse tipo, acesse a documentação oficial do TypeScript.
Conclusão
Chegamos ao fim do artigo! Junto com a primeira parte do artigo, este conteúdo forma uma base sólida para compreender conceitos mais avançados de TypeScript. Novamente, uma recomendação para praticar esses fundamentos é pegar projetos antigos feitos somente em JavaScript, e tentar refatorar utilizando esses conceitos. Em caso de dúvidas, pode colocar nos comentários e responderei o mais rápido possível!
Caso queira, aqui está a documentação oficial do TypeScript.
Obrigado!
Top comments (0)