Typescript 101 - [0]
Começando uma série sobre o querido Typescript
Introdução
Antes de tudo, vamos prestar bastante atenção no que diz o site do typescript.
TypeScript is a typed superset of JavaScript that compiles to plain JavaScript.
Ou em português...
TypeScript é um superconjunto tipado de JavaScript compilado para JavaScript simples.
Então essa série é sobre um superset de Javascript e não uma nova linguagem. Não vou dizer que a migração é super tranquila pois o transpilador (sim, é transpilador pois não gera código de máquina) do Typescript impede por padrão algumas das besteiras que fazemos com Javascript, sejam elas acidentais ou não.
Sem mais delongas e histórias do que é o Typescript, vamos pro código
Getting started
Simples e direto ao ponto:
npm install -g typescript
Se você quiser tentar algo sem precisar instalar, tenta o playground online.
É isso.
Tipos primitivos
Se você veio do Java, C#, F#, Haskell ou qualquer linguagem tipada, então já está em casa. Se você é javascriptero de primeira viagem, seja bem vindo ao mundo onde você sabe EXATAMENTE o que é o que.
JsDoc não é tipagem e nem todos os editores conseguem saber o que são suas variáveis, objetos, quais os tipos do seu array e afins.
Como tipos primários nós temos:
- number: 42, 0xf042, 0b1010, 0o742
- string: "Hack the planet", "Hello World", 'typescript'
- boolean: true, false
- null: algo que existe e não possui valor
- undefined: algo que possivelmente não existe ou não foi definido
Tipos primitivos são os tipos implementados da linguagem. Apesar de []
(array) e {}
(objetos) serem tipos básicos em JS, não vou abordar sobre eles nesse momento para não confundir com termos como inferência ou tipos condicionais.
Como esses 4 tipos aí já podemos começar a garantir algumas seguranças no nosso código através dos tipos
// @ts-nocheck
type type NomePessoa = "Fu" | "Bá" | "Joãozinho" | "Mariazinha" | "Fulano"
NomePessoa =
| "Fu"
| "Bá"
| "Joãozinho"
| "Mariazinha"
| "Fulano";
// Type '"John"' is not assignable to type 'NomePessoa'
const const pessoa: NomePessoa
pessoa: type NomePessoa = "Fu" | "Bá" | "Joãozinho" | "Mariazinha" | "Fulano"
NomePessoa = "John"; //
Já começou dando merda?
Não, calma aí. O nosso tipo NomePessoa
é um tipo que pode ser "Fu" ou "Bá" ou "Joãozinho" ou "Mariazinha" ou "Fulano". Sacou? "John" não é um tipo aceito, então ele não pode ser considerado um NomePessoa
.
Entendeu o segurança que eu disse? Maneiro né. Mas...Typescript é nos garante que isso funcione em desenvolvimento, mas em runtime isso não pode ser garantido caso o input seja algo que não esteja no nosso type. Se você escrever um código que garanta que o input não vai ser uma string
genérica, mas sim um NomePessoa
, você pode ficar despreocupado.
Mas cara, como eu posso garantir que meu código fique blindado com essas paradas?
A resposta é simples, não pode. Mas podemos evitar ao máximo que o input seja malicioso.
// @ts-nocheck
type type NomePessoa = "Fu" | "Bá" | "Joãozinho" | "Mariazinha" | "Fulano"
NomePessoa = "Fu" | "Bá" | "Joãozinho" | "Mariazinha" | "Fulano";
function function getChar(pessoa: NomePessoa, char: number): number
getChar(pessoa: NomePessoa
pessoa: type NomePessoa = "Fu" | "Bá" | "Joãozinho" | "Mariazinha" | "Fulano"
NomePessoa, char: number
char: number) {
return pessoa: NomePessoa
pessoa.String.charCodeAt(index: number): number
Returns the Unicode value of the character at the specified location.charCodeAt(char: number
char);
}
function getChar(pessoa: NomePessoa, char: number): number
getChar("Fu", 2); // Isso passa
function getChar(pessoa: NomePessoa, char: number): number
getChar("FULANO", 0); // Isso dá erro
HMMMMMMMMMMMMMMMMM AGORA EU ENTENDI.
Em desenvolvimento, o transpilador do Typescript irá impedir você passar qualquer valor diferente de NomePessoa
para a sua função getChar
. Da mesma maneira que ele irá garantir que o primeiro parâmetro seja uma string do tipo NomePessoa
, ele vai garantir que seu segundo parâmetro seja um número e não uma string numérica.
Ainda há o caso onde você cria uma variável do tipo string
, que será genérica. E depois irá querer que ela seja usada nessa função. Mas nada pode garantir que ela realmente seja uma string caso o valor não seja controlado ou validado por você.
Inferência
Nem sempre quando for atribuir valor as suas variáveis e atributos, você irá precisar definir o tipo delas. O transpilador do Typescript é inteligente o suficiente para saber seus tipos através do tipo do valor que você está passando.
// @ts-nocheck
const const a: string
a: string = "a"; // isso é o mesmo que
const const a: "a"
a = "a"; // Aqui ele vai inferir o tipo
// Vamos adicionar complexidade aqui pra causar dúvida
const const numbers: number[]
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // Qual é esse tipo?
// numbers é o mesmo que
const const numbers: number[]
numbers: number[] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // Que é o mesmo que
const const numbers: number[]
numbers: interface Array<T>
Array<number> = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9];
Calma aí cara. Já colocou array no bagulho. Explica isso
Tipar arrays é muito simples, e existem duas formas que são quase idênticas. Uma é usando o tipo Array
e outro é usando o seu shorthand
, como já vimos, é o []
. A diferença deles é apenas essa:
// @ts-nocheck
let let shorthand: readonly number[]
shorthand: readonly number[]; // Isso dá bom
let let array: number[]
array: readonly interface Array<T>
Array<number>; // Isso dá ruim
O erro apresentado para o Array<number>
é 'readonly' type modifier is only permitted on array and tuple literal types.(1354)
.
Como não vou entrar no mérito do readonly agora (apesar de ser bem explicito dizendo que o valor é somente leitura e não deve ser sobrescrito), vamos passar para a próxima.
Objetos
Como falei dos arrays acima, agora irei falar de objetos. De cara, veja esse objeto bem simples:
// @ts-nocheck
const const user: {
name: string;
age: number;
languages: string[];
}
user = {
name: string
name: "Fu",
age: number
age: 20,
languages: string[]
languages: ["Javascript", "Typescript", "C#", "Java"],
};
Se você observar nesse playground, ao interagir com a variável user
, você vai ver que consegue autocompletar com os tipos desse objeto, isso graças a inferência do tipo de user
.
Mas e se você quiser garantir que os tipos de user sejam exatamente o que você quer? Assim como era em NomePessoa
. Bom, aí é só começarmos a tipar nossos objetos antes de atribuir os valores. Vou apresentar o tipo aqui, e no playground você pode ver que já temos alguns exemplos
// @ts-nocheck
type type Linguagens = "Javascript" | "Typescript" | "C#" | "Java"
Linguagens = "Javascript" | "Typescript" | "C#" | "Java";
type type User = {
name: NomePessoa;
age: 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
languages: Linguagens[];
}
User = {
name: NomePessoa
name: type NomePessoa = /*unresolved*/ any
NomePessoa;
// faixa de idade permitida: 18 à 25
age: 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25
age: 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
languages: Linguagens[]
languages: type Linguagens = "Javascript" | "Typescript" | "C#" | "Java"
Linguagens[];
};
Com nosso novo tipo User
e Linguagens
iremos conseguir tipar as nossas chaves e valores de cada objeto que seja do tipo User
.
Fica de bônus...
Como falei do readonly
mais acima, vamos usa-lo aqui para garantir imutabilidade ao nosso objeto. Ou seja, toda vez que alguém tentar reatribuir um valor a propriedade de User
, irá ganhar um erro para evitar isso.
Para isso, basta acrescentar o readonly
antes das chaves do nosso objeto, assim podemos escolher quais delas serão imutáveis. Ou utilizar o tipo utilitário do Typescript, chamado Readonly
. Isso dá uma sintáxe um pouco diferente:
// sintáxe readonly por chave
type User = {
readonly name: NomePessoa;
// faixa de idade permitida: 18 à 25
readonly age: 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
readonly languages: Linguagens[];
};
type ReadonlyUser = Readonly<{
readonly name: NomePessoa;
// faixa de idade permitida: 18 à 25
readonly age: 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25;
readonly languages: Linguagens[];
}>;
const user2: ReadonlyUser = {
name: "Fu",
age: 20,
languages: ["Javascript", "Typescript", "C#", "Java"],
};
user2.name = "Chitão"; // Cannot assign to 'name' because it is a read-only property.(2540)
Assim você garante que seus valores nunca irão ser reatribuídos. Em caso de objetos e arrays, o valor pode mudar através dos seus métodos, mas nunca poderão ser reatribuídos.
Conclusão
A primeira parte fica por aqui, e espero que você esteja igual ao Bender...
Planejo continuar essa série para explicar alguns tipos complexos, até chegar em algo parecido com o meu hook use-typed-reducer. Se tiver dúvidas, pode abrir uma issue no repositório ou me procurar no telegram @g4rcez.