TypeScript: O guia definitivo

Um guia completo para sua jornada TypeScript

Image for post
Image for post

Índice

Instalação e uso

Configuração

{
"version": "2.1.5",
"compilerOptions": {
"declaration": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"module": "umd",
"moduleResolution": "node",
"outDir": "_build/",
"removeComments": false,
"sourceMap": true,
"strict": true,
"target": "es5",
"lib": ["es2015", "dom"]
},
"include": [
"./src/**/*.ts",
"./tests/**/*.ts",
"./typings/index.d.ts"
]
}
/// <reference lib="es2016.array.include" />

[ 'foo', 'bar', 'baz' ].includes('bar'); // true

Utilizando let e const

Importação e exportação

import myModule from './myModule';
export function foo() {}
export let bar = 123;
export class MyClass {}
import * from './myModule';

foo();
bar + 5; // = 128
new MyClass();
import { foo } from './myModule';
import { bar, MyClass } from './myModule';

foo();
bar + 5; // = 128
new MyClass();
export default class MyClass {}
import MyClass from './MyClass';

let myInstance = new MyClass();
// AnotherClass = MyClass from MyClass.ts
import AnotherClass from './MyClass';

import { foo, bar as baz } from './myModule';
import * as foo from './myModule';

foo.foo();
foo.bar;
new foo.MyClass();

Açucares de linguagem

function getRange(max, min, exclusive?) {
// ...
}
function getRange(max, min = 0, exclusive = false) {
// ...
}
function publish(topic, ...args): void {
// ...
}
let obj = {
arrow: () => {
console.log(this);
},
regular: function () {
console.log(this);
}
};

obj.arrow(); // logs `window`
obj.regular(); // logs `obj`
const foo = 'foo';
let a = {
// notação abreviada de identificador, igual à `foo: foo`
foo,

// notação abreviada para métodos, igual à `bar: function () {}`
bar() {

}
};

Desestruturação

let x, y;
[x, y] = [10, 20];
let [x, y] = [10, 20];
let { place, location: [x, y] } = { place: 'test', location: [10, 20] };
// variável local 'place' = 'test'
// variável local 'x' = 10
// variável local 'y' = 20

Outros recursos da linguagem

Tipos

function toNumber(numberString: string): number {
const num: number = parseFloat(numberString);
return num;
}
function numberStringSwap(value: any, radix: number = 10): any {
if (typeof value === 'string') {
return parseInt(value, radix);
}
else if (typeof value === 'number') {
return String(value);
}
}

const num = <number> numberStringSwap('1234');
const str = numberStringSwap(1234) as string;

Tipos de Objeto

let point: {
x: number;
y: number;
};
let point: { x: number; y: number; };

point = { x: 0, y: 0 };
// OK, mesmas propriedades

point = { x: 'zero', y: 0 };
// Erro, tipo da propriedade `x` está errado

point = { x: 0 };
// Erro, faltando propriedade `y`

point = { x: 0, y: 0, z: 0 };
// Erro, atribuição literal deve contar apenas propriedades declaradas

const otherPoint = { x: 0, y: 0, z: 0 };
point = otherPoint;
// OK, propriedades extras não são relevantes para atribuição não literal
let point: { x: number; y: number; };
let point2: { x: number; y: number; };
let point: { x: number; y: number; };
let point2: typeof point;
interface Point {
x: number;
y: number;
}

let point: Point;
let point2: Point;
interface Point3d extends Point {
z: number;
}
interface Point {
x: number;
y: number;
z?: number;
}
let point: Point;

point = { x: 0, y: 0, z: 0 };
// OK, mesmas propriedades

point = { x: 0, y: 0 };
// OK, mesmas propriedades, parâmetro opcional omitido

point = { x: 0, y: 0, z: 'zero' };
// Erro, tipo da propriedade `z` está errado
interface Point {
x: number;
y: number;
z?: number;
toGeo(): Point;
}
interface Point {
// ...
toGeo?(): Point;
}
interface HashMapOfPoints {
[key: string]: Point;
}
const Foo = 'Foo';
const Bar = 'Bar';
const Baz = Symbol();

interface MyInterface {
[Foo]: number;
[Bar]: string;
[Baz]: boolean;
}

Tipo “object” em TS 2.2+

Tipos de Tupla

let point: [ number, number, number ] = [ 0, 0, 0 ];
function draw(...point: [ number, number, number? ]): void {
const [ x, y, z ] = point;
console.log('point', ...point);
}

draw(100, 200); // loga: point 100, 200
draw(100, 200, 75); // loga: point 100, 200, 75
draw(100, 200, 75, 25); // Erro: Espera 2-3 argumentos mas recebeu 4

Tipos de Função

let printPoint: {
(point: Point): string;
};
let printPoint: (point: Point) => string;
let printPoint: (point: Point): string
interface Point {
x: number;
y: number;
z?: number;
toGeo: () => Point;
}
let Point: { new (): Point; };
let ShorthandEquivalent: new () => Point;
let Point: {
new (): Point;
fromLinear(point: Point): Point;
fromGeo(point: Point): Point;
};

Funções sobrecarregadas

function numberStringSwap(value: any, radix: number):any {
if (typeof value === 'string') {
return parseInt(value, radix);
}
else if (typeof value === 'number') {
return String(value);
}
}
function numberStringSwap(value: number, radix?: number): string;
function numberStringSwap(value: string): number;
function numberStringSwap(value: any, radix: number = 10): any {
if (typeof value === 'string') {
return parseInt(value, radix);
}
else if (typeof value === 'number') {
return String(value);
}
}
function numberStringSwap(value: any): any;
function numberStringSwap(value: number): string;

numberStringSwap('1234');
let numberStringSwap: {
(value: number, radix?: number): string;
(value: string): number;
};
createElement(tagName: 'a'): HTMLAnchorElement;
createElement(tagName: 'abbr'): HTMLElement;
createElement(tagName: 'address'): HTMLElement;
createElement(tagName: 'area'): HTMLAreaElement;
// ... etc.
createElement(tagName: string): HTMLElement;

Tipos rigorosos para Funções em TS 2.6+

class Animal { breath() { } }
class Dog extends Animal { bark() {} }
class Cat extends Animal { meow() {} }

let f1: (x: Animal) => void = (x: Animal) => x.breath();
let f2: (x: Dog) => void = (x: Dog) => x.bark();
let f3: (x: Cat) => void = (x: Cat) => x.meow();

f1 = f2;
const c = new Cat();
f1(c); // Erro no Runtime

Tipos Genéricos

interface Arrayish<T> {
map<U>(callback: (value: T, index: number, array: Arrayish<T>) => U, thisArg?: any): Array<U>;
}
const arrayOfStrings: Arrayish<string> = [ 'a', 'b', 'c' ];

const arrayOfCharCodes: Arrayish<number> = arrayOfStrings.map(function (value: string): number {
return value.charCodeAt(0);
});
function createArrayish(): Arrayish<string>;
function createArrayish<T>(...args: T[]): Arrayish<T>;
function createArrayish(...args: any[]): Arrayish<any> {
return args;
}
function createArrayish<T = string>(...args: T[]): Arrayish<T> {
return args;
}

Tipos de União

function byId(element: string | Element): Element {
if (typeof element === 'string') {
return document.getElementById(element);
}
else {
return element;
}
}

Tipos de Intersecção

interface Foo {
name: string;
count: number;
}

interface Bar {
name: string;
age: number;
}

export type FooBar = Foo & Bar;
interface Foo {
count: string;
}

interface Bar {
count: number;
}

export type FooBar = Foo & Bar;
/* FooBar.count é do tipo `string & number` */

Tipando o valor de “this”

Tipos Mapeados em TS 2.1+

type Stringify<T> = {
[P in keyof T]: string;
};

interface Point { x: number; y: number; }

type StringPoint = Stringify<Point>;

const pointA: StringPoint = { x: '4', y: '3' };
// válido

Tipos “Mapped”, “Partial”, “Readonly”, “Record” e “Pick” em TS 2.1+

setState(this: StoreMixin, newState: Partial<State>): void {
const { properties: { store, id } } = this;

if (id || newState['id']) {
store.patch(assign( { id }, newState))
.then(() => id ? store.get(id) : store.fetch())
.then((state: State) => {
replaceState(this, state);
});
}
else {
throw new Error('Unable to set state without a specified `id`');
}
}
type ToPomise<T> = { [K in typeof T]: Promise<T[K]> };
type MutableRequired<T> = { -readonly [P in keyof T]-?: T[P] };
type ReadonlyPartial<T> = { +readonly [P in keyof T]+?: T[P] };

interface Point { readonly x: number; y: number; }

const pointA: ReadonlyPartial<Point> = { x: 4 };
pointA.y = 3; // Erro: readonly

const pointB: MutableRequired<Point> = { x: 4, y: 3 };
pointB.x = 2; // Válido
type ToPromise<T> = { [K in typeof T]: Promise<T[K]> };
type Point = [ number, number ];
type PromisePoint = ToPromise<Point>;

const point: PromisePoint = [ Promise.resolve(2), Promise.resolve(3) ]; // Válido
}

Tipos Condicionais em TS 2.8+

declare function addOrConcat(x: number | string): number | string;
declare function addOrConcat(x: string): string;
declare function addOrConcat(x: number): number;
declare function addOrConcat(x: number | string): number | string;
declare function addOrConcat<T extends number | string>(x: T): T extends number ? number : string;
type ExcludeExample = Exclude<string | number | Point, string | Point>; // number
type ExtractExample = Extract<string | number | Point, string | Point>; // string | Point
type NonNullableExample = NonNullable<string | number | null | undefined | void>; // string | number
type ReturnTypeExample = ReturnType<() => boolean>; // boolean

class Renderer {}
type InstanceOfExample = InstanceType<typeof Renderer>; // Renderer

Guardas de Tipo

Utilizando “typeof” como guarda de tipos

function lower(x: string | string[]) {
if (typeof x === 'string') {
// `x` é garantido ser uma `string`, podemos utilizar `.toLowerCase`
return x.toLowerCase();
} else {
// caso contrário, `x` é um array de strings e podemos utilizar `.reduce`
return x.reduce((val: string, next: string) => {
return val += `, ${next.toLowerCase()}`;
}, '');
}
}

Utilizando “instanceof” como guarda de tipos

function clearElement(element: string | HTMLElement) {
if (element instanceof HTMLElement) {
// garantimos que `element` é do tipo `HTMLElement`
// então podemos acessar a propriedade `.innerHTML`
element.innerHTML = '';
} else {
// caso contrário, `element` é uma `string` e podemos passá-la diretamente para `document.querySelector`
const el = document.querySelector(element);
el && el.innerHTML = '';
}
}

Utilizando “in” como guarda de tipos

interface Point {
x: number;
y: number;
}

interface Point3d extends Point {
z: number;
}

function plot(point: Point) {
if ('z' in point) {
// point é um `Point3D`
// ...
} else {
// point é um `Point`
// ...
}
}

Classes

class Proxy {
constructor(kwArgs: {}) {
for (let key in kwArgs) {
this[key] = kwArgs[key];
}
}

get(key: string):any {
return this[key];
}

set(key: {}): void;
set(key: string, value: any): void;
set(key: any, value?: any): void {
// ...
}
}
class Stateful extends Proxy {
constructor(kwArgs: {}) {
super(kwArgs);
}

get(key: string): any {
let getter: string = '_' + key + 'Getter';
return this[getter] ? this[getter]() : super.get(key);
}
}
class Animal extends Stateful {
protected _happy: boolean;

pet(): void {
this._happy = true;
}
}

class Dog extends Animal {
static isDogLike(object: any): boolean {
return object.bark && object.pet;
}

private _loudBark: boolean;

bark(): string {
let noise = this._happy ? 'woof' : 'grr';
if (this._loudBark) {
noise = noise.toUpperCase();
}

return noise;
}
}
class DomesticatedDog extends Dog {
age: number = Math.random() * 20;
collarType: string = 'leather';
toys: Toy[] = [];
}
class DomesticatedDog extends Dog {
age: number;
collarType: string;
toys: Toy[];

constructor(kwArgs: {}) {
this.age = Math.random() * 20;
this.collarType = 'leather';
this.toys = [];
super(kwArgs);
}
}

Mixins e Herança múltipla

interface Chimera extends Dog, Lion, Monsterish {}

class MyChimera implements Chimera {
bark: () => string;
roar: () => string;
terrorize(): void {
// ...
}

// ...
}
MyChimera.prototype.bark = Dog.prototype.bark;
MyChimera.prototype.roar = Lion.prototype.roar;

Enumeráveis

enum Style {
NONE = 0,
BOLD = 1,
ITALIC = 2,
UNDERLINE = 4,
EMPHASIS = Style.BOLD | Style.ITALIC,
HYPERLINK = Style.BOLD | Style.UNDERLINE
}

if (value & Style.BOLD) {
// cuidando de BOLD, EMPHASIS, e HYPERLINK
}
enum Color {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}

Aliases — “Pseudônimos” em Português

import * as foo from './foo';
type Foo = foo.Foo;
type Bar = () => string;
type StringOrNumber = string | number;
type PromiseOrValue<T> = T | Promise<T>;

function convert(value: StringOrNumber): string {
return String(value);
}

function when<T>(value: PromiseOrValue<T>): Promise<T> {
if (value instanceof Promise) {
return value;
}
return Promise.resolve(value);
}

Declarações de Ambiente

// ...

(selector: string, context?: any): JQuery;
(element: Element): JQuery;
(object: {}): JQuery;
(elementArray: Element[]): JQuery;
(object: JQuery): JQuery;
(func: Function): JQuery;
(): JQuery;

// ...
}

declare let jQuery: JQueryStatic;
declare let $: JQueryStatic;
declare module 'dojo/_base/array' {
let array: {
every<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
filter<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean, thisArg?: any): T[];
forEach<T>(array: T[], callback: (value: T, index: number, array: T[]) => void, thisArg?: any): void;
indexOf<T>(array: T[], value: T, fromIndex?: number, findLast?: boolean): number;
lastIndexOf<T>(array: T[], value: T, fromIndex?: number): number;
map<T>(array: T[], callback: (value: T, index: number, array: T[]) => T, thisArg?: any): T[];
some<T>(array: T[], callback: (value: T, index: number, array: T[]) => boolean, thisArg?: any): boolean;
};
export = array;
}
/// <reference path="jquery" />

Carregador de Plugins

/// <amd-dependency path="text!foo.html" />
declare let require: (moduleId: string) => any;
const foo: string = require('text!foo.html');
/// <amd-dependency path="text!foo.html" name="foo" />
declare let foo: string;
declare module "json!*" {
let json: any;
export default json;
}

import d from "json!a/b/bar.json";
// lookup:
// json!a/b/bar.json
// json!*

React e suporte ao JSX

Análise de controle de fluxo

Conclusão

Continuando sua aprendizagem

⭐️ Créditos

Written by

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store