DEV Community

Cover image for Tipos básicos do TypeScript - Parte 2
Marcelo
Marcelo

Posted on

Tipos básicos do TypeScript - Parte 2

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

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' });
Enter fullscreen mode Exit fullscreen mode

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' });
Enter fullscreen mode Exit fullscreen mode

Podemos usar o type alias para qualquer tipo, inclusive union types:

type Age = string | number;
Enter fullscreen mode Exit fullscreen mode

💡 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');
Enter fullscreen mode Exit fullscreen mode

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' });
Enter fullscreen mode Exit fullscreen mode

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, no type é 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;
Enter fullscreen mode Exit fullscreen mode

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'.
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

💡 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!
Enter fullscreen mode Exit fullscreen mode

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"
Enter fullscreen mode Exit fullscreen mode

Uma boa maneira de lembrar do conceito de literal types é comparar com as variáveis JavaScript:

  • let e var permitem alterar o conteúdo da variável, sendo assim, seu tipo seria mais geral, uma string, 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";
Enter fullscreen mode Exit fullscreen mode

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'.
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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"'.
Enter fullscreen mode Exit fullscreen mode

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");
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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'.
}
Enter fullscreen mode Exit fullscreen mode

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());
}
Enter fullscreen mode Exit fullscreen mode

💡 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,
}
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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)