Estudando TypeScript e documentando aqui
Typescript
Antes de qualquer coisa você ira precisar de:
O TypeScript foi criado para deixar o javascript mais declarativo e tipado (declarando os tipos das variáveis), reduzindo assim alguns erros comuns, pois os erros são vistos em runtime, antes mesmo de serem compilados.
Dá pra instalar o TypeScript através do NPM de maneira global no computador através do comando:
npm i -g typescript
Explicando:
npm: gerenciador de pacotes node
i: alias para install
-g: alias para --global (permite instalar em nível global na máquina)
typescript: pacote node de typescript
para rodar o transpilador usamos o comando tsc no terminal passando o arquivo ou diretório que queremos transpilar para javascript
ao final teremos algo do tipo:
qualquerArquivo.ts -> qualquerArquivo.js
// lembrando que podemos especificar tanto o arquivo quanto sua extensão, pois podemos usar .ts .tsx e assim por diante
nota: o transpilador recebe alguns parâmetros por padrão, mas que podem ser alterados no próprio terminal, como por exemplo:
tsc qualquerArquivo.ts --target "ES2019"
# aqui o transpilador ira transformar o código typescript em javascript de acordo com o ECMAScript 2019, ou podemos criar um arquivo chamado tsconfig.json e declarar quais as opções e features que queremos usar.
Para criar um arquivo JSON do TSConfig, podemos usar o comando:
tsc --init
# o comando gerará o output em json de acordo com o seu projeto
nota: caso queira ver as outras opções que o transpilador oferece, é somente digitar:
tsc
# que irá aparecer uma lista de comandos suportados
Variáveis
As variáveis em TypeScript se aplicam da mesma forma que em JavaScript, mudando apenas a inferência de tipos na sua inicialização:
let x: number; // inferindo o tipo number sem inicializar
let y = 1; // tipo number implícito por atribuição
let z; // tipo any implícito
x = 1; // pode receber o valor desde que seja um número
x = "one"; // não pode receber string, pois possui tipo number
y = "one"; // não pode receber string, pois possui tipo number implícito
z = 1; // pode receber qualquer valor a partir de any
z = "one"; // pode receber qualquer valor a partir de any
Tipos
Sobre os tipos temos alguns exemplos a seguir:
// booleans, verdadeiro ou falso (0 ou 1)
let flag: boolean; // inferindo o tipo boolean sem inicializar
let yes = true; // tipo boolean implícito por atribuição e inicializando com o valor true(verdadeiro)
let no = false; // tipo boolean implícito por atribuição e inicializando com o valor false(falso)
// number and bigInt, números podem ser valores de ponto flutuante (1, 1.2, etc.), já os bigInteger podem ser representados por números imutáveis que não possuem limites superiores ou inferiores
let x: number; // inferindo o tipo number sem inicializar
let y = 0; // tipo number implícito por atribuição e inicializando com o valor 0
let z: number = 123.456; // inferindo o tipo number e inicializando com o valor 123.456
let big: bigint = 100n; // inferindo o tipo bigInt e inicializando o valor 100n
// string, tipo de cadeia de caractére, podendo ser usado com aspas duplas, simples e crase (template literals)
let s: string; // inferindo o tipo string sem inicializar
let empty = ""; // tipo string implícito por atribuição e inicializando com o valor ""(string vazia com aspas duplas)
let abc = 'abc'; // tipo string implícito por atribuição e inicializando com o valor 'abc'(string com aspas simples)
// Exemplo com template literals
let myName = "Diana";
let phrase = `My name is ${myName}`; // My name is Diana
// void, null & undefined, todos esse tipos são tipos de valor ausente
Enum
Os tipos enumerados representam um conjunto de constantes relacionadas. Resumindo, um conjunto de valores.
// Exemplo:
// criado conjunto enumerado chamado Fruits com os valores Apple, Banana & Coconut. Os índices dentro da enum começam em 0 por padrão, mas podemos alterar declarando esse valor como por exemplo: Apple = 1;
enum Fruits {
Apple, // índice 0 por padrão
Banana, // índice 1
Coconut // índice 2
}
// podemos então criar uma variável e inferir o nosso contrato Fruits à ela
let fruit: Fruits;
// e podemos inicializar a variável fruit com algum valor pré estabelecido na enum
let fruit: Fruits = Fruits.Apple;
console.log(fruit); // 0 (aqui é impresso o índice do valor)
// se quisermos imprimir nome do valor podemos usar:
console.log(Fruits[fruit]); // Apple
Any e Unknow
Esses tipos podem ser usados quando ainda não temos a certeza de que tipo de dado estamos tratando.
// o tipo any infere que qualquer valor conhecido do JavaScript pode ser atribuído a variável que recebe esse tipo de dado
let randomValue: any = 10; // inferindo o tipo any e inicializando com o valor 10
randomValue = 'Diana'; // reatribuindo o valor 'Diana' à variável randomValue(nesse caso trocando o valor inicial number para string)
randomValue = true; // reatribuindo o valor true à variável randomValue(nesse caso trocando o valor de string para boolean)
// entretanto, devemos nos atentar ao último tipo de dado atribuido, pois no caso acima, um boolean, não pode ser tratado como uma função por exemplo, ou como um objeto
console.log(randomValue.name); // erro - pois a prop name não existe dentro do valor boolean
randomValue(); // erro - pois a variável tem seu tipo boolean e não pode ser uma chamada de função
randomValue.toUpperCase(); // erro - pois a variável tem seu tipo boolean e não pode receber métodos aplicados ao tipo string
Nota: o tipo any pode ser menos seguro no desenvolvimento, portanto deve ser usado com menos frequência
Já o tipo unknown funciona semelhante ao any, com a diferença de que você não terá acesso às propriedades de uma variável definida como tal
let randomValue: unknown = 10; // inferindo o tipo unknown e inicializando com o valor 10
randomValue = true; // reatribuindo o valor true à variável randomValue(nesse caso trocando o valor de string para boolean)
randomValue = 'Diana'; // reatribuindo o valor 'Diana' à variável randomValue(nesse caso trocando o valor inicial number para string)
console.log(randomValue.name); // erro - pois o objeto em questão possui tipo unknown
randomValue(); // erro - pois o objeto em questão possui tipo unknown
randomValue.toUpperCase(); // erro - pois o objeto em questão possui tipo unknown
Asserção de tipos
Podemos então tratar uma variável que possua tipos de dados diferentes, usando a declaração de tipo, uma forma de dizer ao compilador que ‘está tudo bem’. Existem duas formas de fazer:
// usando a palavra reservada as ou usando a sintaxe de colchete do angular
let randomValue: unknown = 10; // inferindo o tipo unknown e inicializando com o valor 10
randomValue = true; // reatribuindo o valor true à variável randomValue(nesse caso trocando o valor de string para boolean)
randomValue = 'Diana'; // reatribuindo o valor 'Diana' à variável randomValue(nesse caso trocando o valor inicial number para string)
// verificando se o tipo da variável randomValue é idêntico a "string"
if (typeof randomValue === "string") {
console.log((randomValue as string).toUpperCase()); // imprimindo o valor "DIANA" no console e usando declaração de tipo com as palavras reservadas as string
} else {
// caso a condição não seja atendida, então irá gerar um erro no console
console.log("Erro - Valor esperado: string");
}
// Ao final será impresso no console 'DIANA', pois a última atribuição foi exatamente com o valor 'Diana', sendo transformado em UpperCase, portanto 'DIANA'
Testando os tipos de dados
Podemos conferir quais os tipos de dados através de um simples if como vimos no exemplo acima, portanto seguem alguns modelos de verificação de tipos:
// string
if (typeof someVariable === "string") {
// ...
}
// number
if (typeof someVariable === "number") {
// ...
}
// boolean
if (typeof someVariable === "boolean") {
// ...
}
// undefined
if (typeof someVariable === "undefined") {
// ...
}
// function
if (typeof someVariable === "function") {
// ...
}
// array
if (Array.isArray(someVariable)) {
// ...
}
União
Podemos inferir mais de um tipo para uma mesma variável no momento de sua criação, tornando assim que os valores a serem atribuídos em seguida sigam algumas especificidades:
let multiType: number | boolean; // inferindo que a variável pode ser number ou boolean
multiType = 20; // tipo number implícito por atribuição e inicializando com o valor 20
multiType = true; // tipo boolean implícito por atribuição e inicializando com o valor true
multiType = "string"; // tipo string não é permitido, pois não foi definido na declaração da variável
// usando um exemplo com uma função
// a função addNumbers recebe dois parâmetros do tipo number ou string
function add(a: number | string, y: number | string) {
// verificando se os parâmetros são do tipo number
if (typeof a === "number" && typeof y === "number") {
// se forem, retorna a soma dos dois parâmetros
return a + y;
} else if (typeof a === "string" && typeof y === "string") {
// se não, retorna a concatenação dos dois parâmetros
return a.concat(y);
} else {
// se não, retorna um erro
throw new Error("Parameters must be numbers or strings");
}
}
// chamando a função add e passando dois parâmetros do tipo number
console.log(add(3, 6)); // 9
// chamando a função add e passando dois parâmetros do tipo string
console.log(add('Diana ', 'Martine')); // Diana Martine
// chamando a função add e passando dois parâmetros de tipos diferentes
console.log(add(3, 'Martine')); // Error: Parameters must be numbers or strings
Interseções
Podemos unir dois ou mais tipos de dados para criar um novo que possua os valores dos anteriores, muito usado com interfaces, veja o exemplo a seguir:
// criando interface IEmployee com os atributos empID, age, salary e name
interface IEmployee {
empID: number;
age: number;
salary: number;
name: string;
}
// criando interface IManager com os atributos stockPlan e reports
interface IManager {
stockPlan: boolean;
reports: IEmployee[];
}
// criando variável do tipo ManagementEmployee que é a união das interfaces IEmployee e IManager
type ManagementEmployee = IEmployee & IManager;
// criando variável employee do tipo ManagementEmployee
// a variável employee deve ter os atributos empID, age, salary, name, stockPlan e reports
let employee: ManagementEmployee = {
empID: 1, // empID é do tipo number
age: 50, // age é do tipo number
salary: 100000, // salary é do tipo number
name: "Diana Martine", // name é do tipo string
stockPlan: true, // stockPlan é do tipo boolean
reports: [
{ empID: 2, age: 30, salary: 80000, name: "Diana Martine" },
{ empID: 3, age: 40, salary: 90000, name: "Diana Martine" },
], // reports é do tipo array de objetos do tipo IEmployee
};
Matrizes
As matrizes são os famosos arrays, representadas por conjuntos de variáveis contidas em colchetes:
let matrix: number[] = [1,2,3];
// Explicando
// Primeiro criamos a nossa variável matrix, e logo após inferimos o tipo dela como uma matriz de números, através da palavra number[], e por último inserimos dados na nossa matriz, respeitando o tipo de dado inferido anteriormente
Entretanto existe outro modo de criar arrays, seguindo a sintaxe do angular, como por exemplo:
let matrix: Array<number> = [1, 2, 3];
// Explicando
// Primeiro criamos uma variável chamada matrix, e logo após inferimos o tipo dela como uma matriz de números, através da sintaxe Array<type>, onde type pode representar qualquer tipo de dado conhecido pelo TypeScript, aqui usamos o tipo number, e logo após inserimos dados na nossa matriz, respeitando o tipo de dado que definimos para ela.
Nota: ao usar qualquer das sintaxes não haverá ganho ou perda, somente cabe a você decidir ˆˆ
Tuplas
Basicamente são matrizes heterogêneas com limite definido, ou seja, que possuem mais de um tipo de dado inserido, como por exemplo:
let tuple: [string, number] = ['Diana', 25];
// Explicando
// Criamos uma variável chamada tuple que é do tipo tupla, ou seja, é um array com um número fixo de elementos. Ao seu primeiro elemento inferimos o tipo string e ao segundo o tipo number. Atribuímos a ela um array com dois elementos, uma string e um number.
// Caso eu queira inserir mais um dado na nossa tupla o TypeScript irá gerar um erro, pois a quantidade de dados é limitada ao declararmos a variável
let tuple: [string, number] = ['Diana', 25, true]; // Erro - apenas serão aceitos dados do tipo string e number, nessa ordem, pois se alterarmos para
let tuple: [string, number] = [25, 'Diana']; // Erro - pois a ordem não corresponde ao que foi declarado