Revisão de TypeScript

--- id: 6724e296dceca21b82426229 title: Revisão de TypeScript challengeType: 31 dashedName: review-typescript --- # --description--

O que é TypeScript

  • JavaScript: JavaScript é uma linguagem de tipagem dinâmica. Isso significa que variáveis podem receber quaisquer valores em tempo de execução. O desafio de uma linguagem de tipagem dinâmica é que a falta de segurança de tipo pode introduzir erros.
Por exemplo, mesmo que sua função JavaScript espere um array, você ainda pode chamá-la com um número:
const getRandomValue = (array) => {
  return array[Math.floor(Math.random() * array.length)];
}

console.log(getRandomValue(10));
A saída do console para o exemplo acima será undefined.
  • TypeScript: TypeScript estende a linguagem JavaScript para incluir tipagem estática, o que ajuda a detectar erros causados por incompatibilidades de tipo antes de você executar seu código.
Por exemplo, você pode definir um tipo para o parâmetro array da seguinte forma:
const getRandomValue = (array: string[]) => {
  return array[Math.floor(Math.random() * array.length)];
}
Esta definição de tipo informa ao TypeScript que o parâmetro array deve ser um array de strings. Então, quando você chama getRandomValue e passa um número, você recebe um erro chamado erro de compilador.
  • Compilador: Você primeiro precisa compilar o código TypeScript em JavaScript comum. Quando você executar o compilador, o TypeScript avaliará seu código e gerará um erro para quaisquer problemas onde os tipos não correspondem.

Tipos de Dados em TypeScript

  • Tipos de Dados Primitivos em TypeScript: Para os tipos de dados primitivos string, null, undefined, number, boolean e bigint, o TypeScript oferece palavras-chave de tipo correspondentes.
let str: string = "Naomi";
let num: number = 42;
let bool: boolean = true;
let nope: null = null;
let nada: undefined = undefined;
  • Array: Você pode definir um array de tipo específico com duas sintaxes diferentes.
const arrOne: string[] = ["Naomi"];
const arrTwo: Array<string> = ["Camperchan"];
  • Objetos: Você pode definir a estrutura exata de um objeto.
const obj: { a: string, b: number } = { a: "Naomi", b: 42 };
Se você quer um objeto com quaisquer chaves, mas onde todos os valores devem ser strings, existem duas maneiras de defini-lo:
const objOne: Record<string, string> = {};
const objTwo: { [key: string]: string } = {};
  • Outros Tipos Úteis no TypeScript:
  • any: any indica que um valor pode ter qualquer tipo. Isso informa ao compilador para parar de se preocupar com o tipo dessa variável.
  • unknown: unknown diz ao TypeScript que você *se importa* com o tipo do valor, mas na verdade não sabe qual é. unknown é geralmente preferido em vez de any.
  • void: Este é um tipo especial que você normalmente usará apenas ao definir funções. Funções que não têm um valor de retorno usam um tipo de retorno void.
  • never: Representa um tipo que nunca existirá.
  • Palavra-chave type: Esta palavra-chave é como const, mas em vez de declarar uma variável, você pode declarar um tipo.
É útil para declarar tipos personalizados como tipos união ou tipos que incluem apenas valores específicos:
type stringOrNumber = string | number;
type bot = "camperchan" | "camperbot" | "naomi";
  • interface: Interfaces são como classes para tipos. Elas podem implementar ou estender outras interfaces, são especificamente tipos de objeto e geralmente são preferidas a menos que você precise de um recurso específico oferecido por uma declaração type.
interface wowie {
  zowie: boolean;
  method: () => void;
}
  • Definindo o Tipo de Retorno: Você também pode definir o *return type* da função.
O exemplo abaixo define o valor de retorno como uma string. Se você tentar retornar qualquer outra coisa, o TypeScript gerará um erro de compilação.
const getRandomValue = (array: string[]): string => {
  return array[Math.floor(Math.random() * array.length)];
}

Genéricos

  • Definindo Tipo Genérico: Você pode definir um tipo genérico e referenciá-lo na sua função. Ele pode ser pensado como um parâmetro especial que você fornece para uma função para controlar o comportamento da definição de tipo da função.
Aqui está um exemplo de definição de um tipo genérico para uma função:
const getRandomValue = <T>(array: T[]): T => {
  return array[Math.floor(Math.random() * array.length)];
}
const val = getRandomValue([1, 2, 4])
A sintaxe <T> informa ao TypeScript que você está definindo um tipo genérico T para a função. T é um nome comum para tipos genéricos mas você pode usar qualquer nome. Então, você informa ao TypeScript que o parâmetro array é um array de valores que correspondem ao tipo genérico, e que o valor retornado é um único elemento desse mesmo tipo.
  • Especificando Argumento de Tipo na Chamada de Função: Você pode passar um argumento de tipo para uma chamada de função usando colchetes angulares entre o nome da função e seus parâmetros.
Aqui está um exemplo de passagem de um argumento de tipo para uma chamada de função:
const email = document.querySelector<HTMLInputElement>("#email");
console.log(email.value);
Isso informa ao TypeScript que o elemento que você espera encontrar será um elemento input.

Refinamento de Tipo

  • Refinamento por Veracidade: No exemplo abaixo, você recebe um erro do compilador ao tentar acessar a propriedade value de email porque email *pode* ser null.
const email = document.querySelector<HTMLInputElement>("#email");
console.log(email.value);
Você pode usar uma declaração condicional para confirmar que email é *truthy* antes de acessar a propriedade:
const email = document.querySelector<HTMLInputElement>("#email");
if (email) {
  console.log(email.value);
}
Verificações de veracidade também podem funcionar na direção oposta:
const email = document.querySelector<HTMLInputElement>("#email");
if (!email) {
  throw new ReferenceError("Could not find email element!")
}
console.log(email.value);
Lançar um erro termina a execução lógica deste código, o que significa que quando você chega à chamada console.log(), o TypeScript sabe que email *não pode* ser null.
  • Encadeamento Opcional: O encadeamento opcional ?. também é uma forma de restrição de tipo, sob a mesma premissa de que o acesso à propriedade não pode acontecer se o valor de email for null.
const email = document.querySelector<HTMLInputElement>("#email");
console.log(email?.value);
  • Operador typeof: Você pode usar uma condicional para verificar o tipo da variável usando o operador typeof.
const myVal = Math.random() > 0.5 ? 222 : "222";
if (typeof myVal === "number") {
  console.log(myVal / 10);
}
  • Palavra-chave instanceof: Se o objeto vem de uma classe, você pode usar a palavra-chave instanceof para restringir o tipo.
const email = document.querySelector("#email");

if (email instanceof HTMLInputElement) {
    console.log(email.value);
}
  • Casting: Quando o TypeScript não consegue determinar automaticamente o tipo de um valor, como o resultado do método request.json() no exemplo abaixo, você receberá um erro do compilador. Uma forma de resolver isso é fazendo o casting do tipo, mas isso enfraquece a capacidade do TypeScript de detectar erros potenciais.
interface User {
    name: string;
    age: number;
}

const printAge = (user: User) => 
  console.log(${user.name} is ${user.age} years old!)

const request = await fetch("url")
const myUser = await request.json() as User;
printAge(myUser);
  • Type Guard: Em vez de fazer o cast do tipo, você pode escrever um type guard:
interface User {
    name: string;
    age: number;
}

const isValidUser = (user: unknown): user is User => {
  return !!user && 
    typeof user === "object" &&
    "name" in user &&
    "age" in user;
}
A sintaxe user is User indica que sua função retorna um valor booleano, que quando verdadeiro significa que o valor user satisfaz a interface User.

Arquivo tsconfig

  • tsconfig.json: As configurações do compilador do TypeScript ficam em um arquivo tsconfig.json no diretório raiz do seu projeto.
{
  "compilerOptions": {
    "rootDir": "./src",
    "outDir": "./prod",
    "lib": ["ES2023"],
    "target": "ES2023",
    "module": "ES2022",
    "moduleResolution": "Node",
    "esModuleInterop": true,
    "skipLibCheck": true,
    "strict": true
  },
  "exclude": ["test/"]
}
Aqui estão as descrições das opções do compilador usadas no exemplo acima:
  • compilerOptions: A propriedade compilerOptions é onde você controla como o compilador TypeScript se comporta.
  • rootDir e outDir: O rootDir e o outDir informam ao TypeScript qual diretório contém seus arquivos fonte e qual diretório deve conter o código JavaScript transpilado.
  • lib: A propriedade lib determina quais definições de tipo o compilador usa e permite que você inclua suporte para versões específicas do ES, o DOM e mais.
  • module e moduleResolution: O module e o moduleResolution trabalham em conjunto para gerenciar como seu pacote usa módulos - seja CommonJS ou ECMAScript.
  • esModuleInterop: O esModuleInterop permite uma interoperabilidade mais suave entre CommonJS e módulos ES ao criar automaticamente objetos de namespace para importações.
  • skipLibCheck: A opção skipLibCheck pula a validação dos arquivos .d.ts que não são referenciados por imports no seu código.
  • strict: A flag strict habilita várias verificações, como garantir o tratamento adequado de tipos anuláveis e avisar quando o TypeScript recorre a any.
  • exclude: A propriedade de nível superior exclude indica ao compilador para ignorar esses arquivos TypeScript durante a compilação.
# --assignment-- Revise os tópicos e conceitos de TypeScript.