Skip to content

Instantly share code, notes, and snippets.

@renatoapcosta
Last active March 3, 2023 23:51
Show Gist options
  • Save renatoapcosta/7e05cda277edf6a49fe58653acd53097 to your computer and use it in GitHub Desktop.
Save renatoapcosta/7e05cda277edf6a49fe58653acd53097 to your computer and use it in GitHub Desktop.
Typescript

Typescript

O typescript foi criada pela Microsoft e tem como objetivo gerar javascript. Esse javascript gerado para ser usado no frontend browser ou no backend node.

Mas porque usar typescrypt?

O typescript pega tudo que já está no javascript e adiciona recursos como polimorfisto, herança, tipagem, escopo.

O typescript já verifica problemas na criação do código.

Preparando o ambiente

Instale o NodeJs e NPM

Vamos instalar o compilador(transpilador) typescript global para executar em qualquer lugar:

npm install -g typescript

tsc -v

É recomendando o uso de uma IDE como exemplo o Visual Studio Code.

Hello World

Precisamos criar um arquivo com a extensão .ts

hello.ts

function printHello(msg: string): void {
    console.log(msg);
}

printHello('Hello World');

Vamos transpilar tsc hello.ts e depois node hello.js

Por padrão o typescrypt compila para versão ES3 mas podemos passar parametros no comando:

tsc hello.ts --target ES2015 ou podemos criar um arquivo de configuração no projeto e configurar qual compilação queremos:

tsconfig.json

{
  "compilerOptions": {
    "target": "ES2015"                                     
  }
}

Mas devemos compilar toda a pasta do projeto para ele detectar as configurações. tsc

Para compilar para uma pasta especifica:

tsc --outDir ./build/ src/aula01.ts

Criando um projeto Typescript

tsc --init

Ele vai criar o arquivo tsconfig.json com algumas configurações para o projeto.

{
  "compilerOptions": {
    "target": "es2016",                                  
    "module": "commonjs",                                
    "esModuleInterop": true,                            
    "forceConsistentCasingInFileNames": true,            
    "strict": true,                                     
    "skipLibCheck": true   
  }
}

Podemos acionar tsc --watch para ele observar as mudança dos arquivos .ts e transpilar para .js.

Configurações tsconfig.json

Para configurar os diretórios do projeto:

"rootDir": "./scripts", //fontes
"outDir": "./output", // destino

Habilitar o source.map para ajudar os navegadores a achar os arquivos: "sourceMap": true,

Podemos carregar libs também: "lib": ["DOM"], - Carregando a lib de DOM

Tipos básicos do typescript

O typescript é Type Inference( Deduz o tipo )

string:

let name: string = 'Ren';

number:

let nroInt: number = 1;
let nroHackzinho = +"400";
let nroFloat: number = 1.67;
let nroHex: number = 0xFA;

union type: (união de dois tipos)

let chavePix: string | number;  

boolean

let sucess: boolean = true;

arrays

let names: string[] = ["a", "b"];
let names2: Array<string> = ["a", "b"];

trupe ( ter um limitando os tipos e quantidades

let tipoTrupe: [string, number, string] = ["a", 1, "b"];

enum

enum EstadoCivil {SOLTEIRO = '1', CASADO = '2'};
let meuEstadoCivil = EstadoCivil.CASADO;
console.log(meuEstadoCivil);  // 2

object

// Usando o tipo object
let obj1: object = {
    name: 'Renato'
};

// obj1 = '1'; error
// obj1 = 2; error
obj1 = []; // OK, um Array é um objeto

// Usando o Object javascript como tipo
let obj2: Object = {
    name: 'Renato'
};

obj2 = "2"; // OK
obj2 = 2; // OK
obj2 = []; // OK

null undefined

let valor: string | null; // O tipo null podemos utilizar em conjunto com union type. O null é a ausencia de valor
let minhaVariavel: string | undefined;  // Também vai funcionar com union type. Ele representa que a variavel não foi definida.

any

let obj3: any = {  name: 'Renato'}; // any é qualquer tipo de dados
obj3 = "2"; // OK
obj3 = 2; // OK
obj3 = []; // OK
console.log(obj3);

unknown

let resultado: unknown; // ele é parecido com o any, mas não podemos redifinir ela para outro tipo
resultado = 1;
resultado = "a";
let a: string = resultado; // não permite

void

const meuConsole: void = console.log(""); // guarda o retorno de nada, usado em retorno de função que não retorna nada

never

 // nunca vai ter um nada, nem void, como uma exception
 function err(): never {
   throw "";
 }

afirmação de tipo

const site: unknown = "hrhrhr";
let sites: string[] = [];
sites.push(site as string);
sites.push(<string>site);

Funções

function sum(x: number, y: number): number {
    return x + y;
}

let a: number = sum(2,3);

//Arrow function

const show: string = (codigo: number): string => {
    return "";
}

Parametros com valor padrão

function sum(x: number = 0, y: number = 0): number {
    return x + y;
}

Parametro com valor opcional

function sum(x: number, y: number, nome?: string = ''): number {
    return x + y;
}

rest parameter

function printer(... msg: string[]) {

}

Interface

Interface é uma estrutura que nos permite definir a forma de objetos.

Ela pode definir o tipo de propriedades, os parâmetros esperados por funções e o tipo do retorno dessas funções.

Em poucas palavras, uma interface cria um padrão, e nos obriga a segui-lo.

interface IProduto {
    nome: string;
    preco: number;
    descricao?: string;
    dataValidade: Date;
}

const produtoDados: IProduto = {
    nome: "Notebook",
    preco: 2000,
    // descricao: "Meu notebook superpotente",
    dataValidade: new Date(2022, 11, 1)
}

Propriedades readonly: Ela faz com que o valor de um atributo não seja mais alterado, depois de inicializado.

interface ICurso {

    readonly titulo: string;
    descricao?: string;
    readonly preco: number;
    cargaHoraria: number;
    classificacao: number;

}

const curso: ICurso = {
    titulo: "TypeScript",
    preco: 5000,
    cargaHoraria: 10,
    classificacao: 5
}

// curso.titulo = "PHP 8";
// curso.preco = 6000;

console.log(curso);

Funções e Interfaces

interface Calculos {
    somar(a: number, b: number): number;
    subtrair(a: number, b: number): number;
    multiplicar(a: number, b: number): number;
    dividir(a: number, b: number): number;
}

let calculadora: Calculos;

function adicao(a:number, b:number) {
    return a + b;
}

const subtrair = (n1: number, n2: number) => n1 - n2;

calculadora = {
    dividir: (numero1: number, numero2: number) => {
        return numero1 / numero2;
    },
    multiplicar: function (numero1: number, numero2: number) {
        return numero1 * numero2;
    },
    somar: adicao,
    subtrair
}

Arrays e Interface

interface Categoria {
    nome: string;
    id: number;
    categoriaPai?: Categoria;
}

const frontEnd: Categoria = {
    nome: "Front-End",
    id: 1
}

const backEnd: Categoria = {
    nome: "Back-End",
    id: 2
}

interface Menu {

    categorias: Categoria[];

}

let menu: Menu = {
    categorias: [frontEnd, backEnd]
};

Estendendo interfaces

interface Modelo {
    id: number;
    created_at: number;
    updated_at: number;
}

interface IPessoa extends Modelo {
    nome: string;
    idade?: number;
}

interface IUsuario extends IPessoa {
    email: string;
    senha: string;
}

const joao: IUsuario = {
    email: "[email protected]",
    id: 1,
    nome: "João Rangel",
    senha: "123456",
    created_at: new Date().getTime(),
    updated_at: new Date().getTime()
}

Orientação a Objetos

A Programação Orientada a Objetos é um paradigma de programação que consiste em criar as instâncias de objetos.

Esse estilo de programação surgiu na década de 1960.

Quando pensamos em objetos do mundo real, percebemos que todos têm algo em comum: características e ações

CARACTERÍSTICAS Cor Marca Memória Câmera

AÇÕES Mandar mensagens Ver filmes Tirar fotos

Como construir um objeto?

Para construir um objeto, é necessário possuir um modelo ( classe )

CLASSE - Conjunto de atributos e métodos, Ela é como uma “planta”, ou “receita”

Além de nos permitir criar instâncias de classes, a Orientação a Objetos funciona com quatro conceitos principais:

  1. Herança
  2. Encapsulamento
  3. Polimorfismo
  4. Abstração

Classes

class Pessoa {

    nome: string;
    idade: number;
    altura: number;

    constructor(nome: string, idade: number, altura: number) {
        this.nome = nome;
        this.idade = idade;
        this.altura = altura;
    }

    toString(): string {
        return `A pessoa ${this.nome} tem ${this.idade} anos e mede ${this.altura} de altura.`;
    }
}

const ronaldo = new Pessoa("Ronaldo Braz", 30, 1.85);
const marcus = new Pessoa("Marcus Ribeiro", 19, 2);

ronaldo.nome = "Ronaldo";

console.log("Pessoa: " + ronaldo);
console.log("Pessoa: " + marcus);

Metodos

class Professor {

    nome: string;
    idade: number;
    materia: string;

    constructor(nome: string, idade: number, materia: string) {
        this.nome = nome;
        this.idade = idade;
        this.materia = materia;
    }

    seApresentar(): string {
        return `Meu nome é ${this.nome}, tenho ${this.idade} anos e vou lecionar ${this.materia}.`;
    }

    dizerNotas(...notas: number[]): number {
        const notasTotal = notas.reduce((total, notaAtual) => total + notaAtual, 0);
        return notasTotal / notas.length;
    }
}

const glaucio = new Professor("Glaucio Daniel", 45, "DBA");
console.log(glaucio.seApresentar());

console.log("==========");
const joaoRangel = new Professor("João Rangel", 25, "TypeScript");
console.log(joaoRangel.seApresentar());
console.log(joaoRangel.dizerNotas(8, 10, 5.5, 7.5));
class Animal {
    name: string ;
    color: string;
    constructor(name: string, color: string) {
        this.name = name;
        this.color = color;
    }
}

let myAnimal = new Animal('Coelho', 'Branco');
console.log(myAnimal);
class Animal {
    constructor(public name: string, public color: string) {
    }
}

let myAnimal = new Animal('Coelho', 'Branco');
console.log(myAnimal);

Herança

O conceito de herança permite que uma classe herde recursos de outra classe.

A classe que irá compartilhar as informações é chamada de classe pai.

A classe que irá herdar as informações é chamada de classe filha.

class Cadastro {

    nome: string;
    nascimento: Date;

    constructor(nome: string, nascimento: Date) {
        this.nome = nome;
        this.nascimento = nascimento;
    }
}

class Cliente extends Cadastro {

    email: string;
    empresa: string;

    constructor(
        nome: string,
        nascimento: Date,
        email: string,
        empresa: string
    ) {
        super(nome, nascimento);
        this.email = email;
        this.empresa = empresa;
    }
}

const clienteJoao = new Cliente(
    "Joao",
    new Date("2000-01-01"),
    "[email protected]",
    "Hcode"
);
console.log(clienteJoao);

Encapsulamento

Encapsulamento nos permite definir diferentes níveis de acesso para nossos atributos e métodos.

Usando os modificadores de acesso, podemos determinar até que ponto um atributo ou método é visível para outras variáveis ou classes.

Existem quatro modificadores de acesso que podemos atribuir aos nossos atributos e três aos métodos:

  1. public
  2. protected
  3. private
  4. readonly (apenas atributo)

public

Atributos e métodos públicos podem ser acessados ou alterados por qualquer classe.

** Quando não colocamos nenhum modificar de acesso, ele é publico implicitamente.

class Veiculo {

    cor: string;

    constructor(cor: string) {
        this.cor = cor;
    }

    public tentarAbrirPorta(): boolean {
        return false;
    }
}

const carro = new Veiculo("Branco");
carro.cor = "Preto";
console.log(carro.cor);
console.log(carro.tentarAbrirPorta());

protected

Atributos e métodos protegidos podem ser acessados ou alterados por meio da classe em que foram criados e por meio das classes que forem filhas dela.

class Domicilio {

    public cor: string;
    public endereco: object;

    constructor(cor: string, endereco: object) {
        this.cor = cor;
        this.endereco = endereco;
    }

    public tocarInterfone(): string {
        return "Interfone tocado!";
    }

    protected atenderInterfone(mensagem: string): string {
        return mensagem;
    }
}

class Casa extends Domicilio {

    public entrarNaCasa() {
        return this.atenderInterfone("Oi, quem é?");
    }
}

const casaDoHomer = new Casa("Rosa", {cidade:"Springfield"});

console.log(casaDoHomer.entrarNaCasa());

private

Atributos e métodos privados são os mais restritos. Eles podem ser acessados ou alterados apenas por meio da classe em que foram criados.

class Banco {

    private cofreQtd: number = 10000;

    private debitarCofre(quantidade: number): number | string {
        if (this.cofreQtd >= quantidade) {
            this.cofreQtd -= quantidade;
            return this.cofreQtd;
        } else {
            return "O cofre não possui a quantidade requisitada";
        }
    }

    protected sacarDoCaixa(quantidade: number) {
        return this.debitarCofre(quantidade);
    }

    sacarDoCaixaEletronico(quantidade: number) {
        return this.debitarCofre(quantidade);
    }
}

class Banco24Horas extends Banco {
    sacar(qtd: number) {
        return this.sacarDoCaixa(qtd);
    }
}

const nubank = new Banco();
const banco24horas = new Banco24Horas();

banco24horas.sacar(2500);

Private no Typescript e ECMAScript

Diferença do private do typescript e o private do ECMAScript.

class Documento {

    private valor: string = "1236544789-01"; // TS
    #numero: number = 35; // ECMAScript

    mostrarDocumento() {
        return this.#numero;
    }
}

class CNPJ extends Documento {

    // private valor: string = "25698745632-01";
    #numero: number = 50;

    mostrarCNPJ() {
        return this.#numero;
    }
}

const rg = new Documento();
console.log(rg.mostrarDocumento());
const cnpj = new CNPJ();
console.log(cnpj.mostrarCNPJ());

//console.log("RG:" + rg.valor);
//console.log("RG:" + rg.#numero);

private x readonly

O readonly é usado somente para atributos, e não em metodos.

Atributos com readonly protegem alterações, mas permitem que sejam vistos e lidos.

class Usuario {
    readonly id: string = "123123sdad232";  // permito ler , mas não modificar
    nome: string = "Joao";
    private senha: string = "123456"; // não permito ler e nem modificar
    readonly dataCadastro: Date = new Date("2021-01-01");

}

const usuario = new Usuario();
// usuario.id = "123";
//console.log(usuario.senha);

Simplificando o metodo construtor

class Pedido {

    constructor(
        private produto: string,
        protected valorTotal: number,
        public previsaoEntrega: Date
    ) { }
}

const meuPedido = new Pedido("TV 64 polegadas", 2000, new Date("2021-05-01"));
console.log(meuPedido);

getter e setter

class Permissao {

    constructor(
        private _nome: string,
        private _nivel: number
    ) {}

    get nome() {
        return this._nome.toUpperCase();
    }

    set nome(novoNome: string) {
        if (novoNome.length < 5) {
            throw new Error("O nome da permissão deve ter no mínimo 5 letras");
        }      
        this._nome = novoNome;
    }
}

const permissao = new Permissao("Administrador", 1);
console.log(permissao.nome);

try {
    permissao.nome = "adm";
} catch (e) {
    console.log(e.message);
}

console.log(permissao.nome);

Propriedade e metodos estaticos

interface IBancoDeDados {
    ip: string;
    usuario: string;
    senha: string;
    tipoBanco: string;
}

class BancoDeDados {

    static LOCAL = "127.0.0.1";
    static TIPO_MYSQL = "MySQL";
    static TIPO_SQLSERVER = "SQL Server";

    constructor(
        private ip: string,
        private usuario: string,
        private senha: string,
        private tipoBanco: string
    ) { }

    static factory(parametros: IBancoDeDados) {

        if (![
            BancoDeDados.TIPO_MYSQL,
            BancoDeDados.TIPO_SQLSERVER
        ].includes(parametros.tipoBanco)) {
            throw new Error("Tipo de banco incorreto");
        }

        return new BancoDeDados(
            parametros.ip,
            parametros.usuario,
            parametros.senha,
            parametros.tipoBanco);
    }

}

const conexaoBanco = BancoDeDados.factory({
    tipoBanco: BancoDeDados.TIPO_MYSQL,
    senha: "root",
    usuario: "root",
    ip: BancoDeDados.LOCAL
});

console.log(conexaoBanco);

Polimorfismo

class Empresa {
    prestarServico() {
        return "Empresa prestando serviço";
    }
}

class Padaria extends Empresa {
    prestarServico() {
        return "Vendendo pão";
    }
}

class Mercearia extends Empresa {
    prestarServico() {
        return "Vendendo alimentos e bebidas";
    }
}

console.log(new Empresa().prestarServico());
console.log(new Padaria().prestarServico());
console.log(new Mercearia().prestarServico());

classes abstrata

interface MeuUsuario {
    nome: string;
    email: string;
    telefone: string;
    idAndroid?: string;
}

abstract class Notificacao {
    abstract enviar(usuario: MeuUsuario): boolean;
}

class Email extends Notificacao {    
    enviar(usuario: MeuUsuario): boolean {        
        console.log(`Enviando e-mail para o usuario ${usuario.email} ...`);
        return true;
    }
}

class SMS extends Notificacao {    
    enviar(usuario: MeuUsuario): boolean {        
        console.log(`Enviando SMS para o usuario ${usuario.telefone} ...`);
        return true;
    }
}

class Android extends Notificacao {    
    enviar(usuario: MeuUsuario): boolean {        
        console.log(`Enviando mensagem para o usuario no Android ${usuario.idAndroid} ...`);
        return true;
    }
}

new Email().enviar({
    nome: "Joao",
    email: "[email protected]",
    telefone: "11912344321"
});

new SMS().enviar({
    nome: "Joao",
    email: "[email protected]",
    telefone: "11912344321"
});

new Android().enviar({
    nome: "Joao",
    email: "[email protected]",
    telefone: "11912344321",
    idAndroid: "sdfgsdgdf"
});

Implementando uma interface em uma classe

interface IEmailV2 {
    nome: string;
    email: string;
}

interface ITelefoneV2 {
    numero: string;
}

interface INotificacaoV2 {
    enviar(usuario: MeuUsuarioV2): boolean;
}

interface MeuUsuarioV2 {
    nome: string;
    email: string;
    telefone: string;
    idAndroid?: string;
}

abstract class NotificacaoV2 {
    abstract enviar(): boolean;
}

class EmailV2 extends NotificacaoV2 implements INotificacaoV2, IEmailV2 {

    nome: string;
    email: string;

    constructor(usuario: MeuUsuarioV2) {
        super();
        this.nome = usuario.nome;
        this.email = usuario.email;
    }
    
    enviar(): boolean {        
        console.log(`Enviando e-mail para o usuario ${this.email} ...`);
        return true;
    }  
}

class SMSV2 extends NotificacaoV2 implements INotificacaoV2, ITelefoneV2 {
    
    numero: string;

    constructor(usuario: MeuUsuarioV2) {

        super();
        this.numero = usuario.telefone;

    }
    
    enviar(): boolean {
        
        console.log(`Enviando SMS para o usuario ${this.numero} ...`);

        return true;

    }    

}

new EmailV2({
    nome: "Joao",
    email: "[email protected]",
    telefone: "11912344321"
}).enviar();

new SMSV2({
    nome: "Joao",
    email: "[email protected]",
    telefone: "11912344321"
}).enviar();

Type

Um type é semelhante a uma classe, mas não tem comportamento, ou seja seria semelhante a uma interface que só possui atributos e neste caso podemos ter um type por questão semantica.

Com type conseguimos comparar diretamente um json.

export type Ranking {
 player: Player
 score: number
 matchDate: Date
 heroes: Hero[]
}

type Player {
  name: string
  country: string
}

type Hero {
  name: string
  level: number
}

Modulos

Um módulo é basicamente um arquivo separado que armazena uma parte de nosso código. Ao fazer a importação de um módulo, podemos reaproveita seu código em vários arquivos.

Ao utilizar módulos, temos acesso a alguns benefícios:

  1. Melhor organização do código
  2. Melhor reutilização do código
  3. Evitar conflito nos identificadores das variáveis (cada módulo tem seu próprio escopo)

Módulos - Por que são necessários?

O resultado é um código mais organizado e fácil de entender e dar manutenção.

./business/produto.ts

export interface Product {
    nome: string;
    code: number;
}
import { Product }  from './business/produto'

const produto: Product = {
 nome: '',
 code: 2
};

./interfaces/IBancoDeDados.ts

export interface IBancoDeDados {
    ip: string;
    usuario: string;
    senha: string;
    tipoBanco: string;
}

./classes/BancoDeDados.ts

import { IBancoDeDados } from "../interfaces/IBancoDeDados";

export class BancoDeDados {

    static LOCAL = "127.0.0.1";
    static TIPO_MYSQL = "MySQL";
    static TIPO_SQLSERVER = "SQL Server";

    constructor(
        private ip: string,
        private usuario: string,
        private senha: string,
        private tipoBanco: string
    ) { }

    static factory(parametros: IBancoDeDados) {

        if (![
            BancoDeDados.TIPO_MYSQL,
            BancoDeDados.TIPO_SQLSERVER
        ].includes(parametros.tipoBanco)) {
            throw new Error("Tipo de banco incorreto");
        }

        return new BancoDeDados(
            parametros.ip,
            parametros.usuario,
            parametros.senha,
            parametros.tipoBanco);
    }
}

./conexao.ts

import { BancoDeDados } from "./classes/BancoDeDados";

const conexaoBanco = BancoDeDados.factory({
    tipoBanco: BancoDeDados.TIPO_MYSQL,
    senha: "root",
    usuario: "root",
    ip: BancoDeDados.LOCAL
});

console.log(conexaoBanco);

Vamos importar uma função em um modulo

./funcoes/mostrarMensagem.ts

export default function mostrarMensagem(texto: string | number | object): boolean {
    console.log(texto);
    return true;
}

Quando eu uso o export default, quando eu importar eu não preciso do destructor:

import { mostrarMensagem } from "./funcoes/mostrarMensagem"; Nao precisa, somente se não tiver o default

import mostrarMensagem from "./funcoes/mostrarMensagem"; OK

./funcoes.ts

import mostrarMensagem from "./funcoes/mostrarMensagem";

console.log(mostrarMensagem("Hcode"));
console.log(mostrarMensagem(100));
console.log(mostrarMensagem({ url: "https://hcode.com.br"}));

const show = (codigo: number): number | Date | object => {
    return new Date();
}

Outro exemplo de organização:

./interfaces/MeuUsuario.ts

export default interface MeuUsuario {
    nome: string;
    email: string;
    telefone: string;
    idAndroid?: string;
}

./classes/Notificacao.ts

import MeuUsuario from "../interfaces/MeuUsuario";

export default abstract class Notificacao {
    abstract enviar(usuario: MeuUsuario): boolean;
}

./classes/Notificacao.ts

import MeuUsuario from "../interfaces/MeuUsuario";
import Notificacao from "./Notificacao";

export class Email extends Notificacao {
    
    enviar(usuario: MeuUsuario): boolean {        
        console.log(`Enviando e-mail para o usuario ${usuario.email} ...`);
        return true;
    }    

}

export class SMS extends Notificacao {
    
    enviar(usuario: MeuUsuario): boolean {        
        console.log(`Enviando SMS para o usuario ${usuario.telefone} ...`);
        return true;
    }    

}

export class Android extends Notificacao {
    
    enviar(usuario: MeuUsuario): boolean {        
        console.log(`Enviando mensagem para o usuario no Android ${usuario.idAndroid} ...`);
        return true;
    }    

}

index.ts

import { Email, SMS, Android } from "./classes/MeiosDeNotificacao";

new Email().enviar({
    nome: "Joao",
    email: "[email protected]",
    telefone: "11912344321"
});

new SMS().enviar({
    nome: "Joao",
    email: "[email protected]",
    telefone: "11912344321"
});

new Android().enviar({
    nome: "Joao",
    email: "[email protected]",
    telefone: "11912344321",
    idAndroid: "sdfgsdgdf"
});

Import Path Alias

./atendiment/client.ts

export class Client { ... }

./atendiment/pessoa.ts

export class Pessoa { .. }

./atendiment/index.ts

export * from './client';
export * from './pessoa';

Usando

import { Client, Pessoa } from './atendiment';

Namespaces

Namespace é um recurso específico do TypeScript que nos permite organizar melhor os arquivos em nossos projetos.

O objetivo dos namespaces é muito similar ao que já conhecemos dos módulos.

O typescript recomenda usar modulos ao invés de namespace.

Mas vamos ver para entender um projeto legado.

./autenticacao/Autenticacao.ts

namespace Autenticacao {

    interface IUsuario {
        email: string;
        senha: string;
    }

    interface ICadastro {
        nome: string;
        email: string;
        senha: string;
        dataNascimento: Date;
    }

    export class LoginRegistro {

        login(usuario: IUsuario) {
            return usuario;
        }

        registro(novoUsuario: ICadastro) {
            return novoUsuario;
        }
    }

    export class Recuperacao {

        recuperarSenha() {
            return "Enviando e-mail para recuperação de senha";
        }

        recuperarUsuario() {
            return "Enviando e-mail para recuperação de nome de usuário";
        }
    }
}

./autenticacao.ts

/// <reference path="./autenticacao/index.ts" />

const novoRegistro = new Autenticacao.LoginRegistro();

const resultadoRegistro = novoRegistro.registro({
    nome: "Djalma",
    email: "[email protected]",
    senha: "djalmaroot",
    dataNascimento: new Date("1995-02-20")
});

console.log(resultadoRegistro);

Mixins

Uma classe só pode extender somente de uma classe. Mas e se quiser extender de duas classes. Os mixins resolve este problema do typescript.

class Movel{}

classe Produto {}

queremos

class Sofa extends Produto, Movel {} // erro

Para isso criamos um interface da classe que queremos:

class Sofa {}
interface Sofa extends Produto, Movel {} 

Mas o typescript fornece uma função que conseguimos fazer isso

./applyMixins.ts

export default function applyMixins(derivedCtor: any, constructors: any[]) {
    constructors.forEach((baseCtor) => {
      Object.getOwnPropertyNames(baseCtor.prototype).forEach((name) => {
        Object.defineProperty(
          derivedCtor.prototype,
          name,
          Object.getOwnPropertyDescriptor(baseCtor.prototype, name) ||
            Object.create(null)
        );
      });
    });
}

./index.ts

import applyMixins from "./applyMixins";

class ProdutoFinal {

    vender(quantidade: number) { 
        return `Foram vendidos ${quantidade} itens deste produto`;
    }

    comprar(quantidade: number) { 
        return `Foram comprados ${quantidade} itens deste produto`;
    }
}

class Movel {

    sentar() {
        return "Você sentou no móvel";
    }

    empurrar(metros: number) {
        return `O móvel foi empurrado ${metros} metros.`
    }
}

class Sofa {
    constructor(public nome: string) { }
}

interface Sofa extends ProdutoFinal, Movel {}
applyMixins(Sofa, [ProdutoFinal, Movel]);

const produto = new Sofa("Meu sofá");

console.log(produto.vender(25));
console.log(produto.empurrar(50));
console.log(produto.nome);

Generics

Generics nos permitem criar estruturas que serão adaptáveis a vários tipos de dados.

Esse conceito nos ajuda a reaproveitar melhor nosso código e torná-lo flexível para diversas situações.

Para definir um Generic, basta informar um tipo genérico usando os sinais de menor e maior < >.

Mesmo que definamos um tipo genérico, continuamos a ter acesso aos recursos do IntelliSense do VS Code.

Podemos criar tipos genéricos ao trabalhar com:

  1. Funções
  2. Interfaces
  3. Classes

Tipos Generics

function obterPrimeiraPosicao<T>(meuArray: Array<T>): T | undefined {
    return meuArray.pop();
}

console.log(obterPrimeiraPosicao(["Glaucio", 10, "Rafael", "Ronaldo"]));

Interfaces Generics

interface IProcessamento<TipoInterface> {
    valor: TipoInterface;
    realizarProcessamento(arg: TipoInterface): TipoInterface;
}

const texto: IProcessamento<string> = {
    valor: "hcode",
    realizarProcessamento(argumento: string): string {
        return argumento.toUpperCase();
    }
}

console.log(texto.valor);
console.log(texto.realizarProcessamento("hcode treinamentos"));

const numero: IProcessamento<number> = {
    valor: 10,
    realizarProcessamento(v: number): number {
        return v * v;
    }
}

console.log(numero.realizarProcessamento(numero.valor));

Classes Generics

interface CadastroPadrao {
    readonly id: number;
    readonly created_at: Date;
    readonly updated_at: Date;
}

interface CadastroUsuario extends CadastroPadrao {
    nome: string;
    email: string;
    senha: string;    
}

interface CadastroCategoria extends CadastroPadrao {
    nome: string;
}

class CadastroBasico<InterfaceBasica> {

    criar(dados: InterfaceBasica): InterfaceBasica {
        console.log("Criando novo registro");
        return dados;
    }

    selecionar(id: number): InterfaceBasica {
        console.log("Dados do registro de ID " + id);
        return {} as InterfaceBasica;
    }

    editar(
        id: number,
        dadosAtualizacao: InterfaceBasica
    ): InterfaceBasica {
        console.log("Editando dados do ID " + id);
        return dadosAtualizacao;
    }

    excluir(id: number): boolean {
        console.log("Excluindo registro do ID " + id);
        return true;
    }

}

class UsuarioModelo extends CadastroBasico<CadastroUsuario> { }

const novoUsuario = new UsuarioModelo();

console.log(novoUsuario.criar({
    id: 1,
    nome: "Glaucio Daniel",
    email: "[email protected]",
    senha: "gl@uc1o",
    created_at: new Date("2015-06-01"),
    updated_at: new Date("2020-01-20")
}));

class CategoriaModelo extends CadastroBasico<CadastroCategoria> { }

const novaCategoria = new CategoriaModelo();

novaCategoria.criar({
    id: 1,
    nome: "JavaScript",
    created_at: new Date("2015-06-01"),
    updated_at: new Date("2020-01-20")
});
class List<T> {
    private items: Array<T> = [];

    public add(item: T): void {
        this.items.push(item);
    }

    public getItem(index: number): T {
        return this.items[index];
    }
}

const list = new List<string>();
list.add('aa');
console.log(list.getItem(0));

Restrições Generics

interface IHcode {
    length: number;
}

function mostrarQuantidadeTotal<T extends IHcode>(valor: T): T {
    console.log(`Total: ${valor.length}`);
    return valor;
}

console.log(mostrarQuantidadeTotal([10, 20]));
console.log(mostrarQuantidadeTotal({ nome: "Ronaldo", length: 1 }));

Decorators

Decorators é um recurso que nos permite realizar modificações em partes de nossos códigos no momento de sua execução. Podemos dizer que eles são como uma anotação adicionada ao código que permite uma modificação em seu comportamento.

Um decorator é basicamente uma função que é chamada em tempo de execução e que realiza uma modificação na estrutura onde foi anotada.

Um decorator pode ser aplicado a:

  1. Parâmetros
  2. Classes
  3. Atributos
  4. Métodos
  5. Getters e Setters

Este é um recurso que já foi proposto para ser integrado de maneira nativa com o JavaScript.

Atualmente, é um recurso experimental no TypeScript.

O framework Angular faz muito isso em suas estruturas.

Antes de desenvolver precisamos habilitar essa feature no typescript no tsconfig.json.

"experimentalDecorators": true

{
  "compilerOptions": {
    "target": "ESNEXT",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
    "lib": ["DOM", "ES2015"],                             /* Specify library files to be included in the compilation. */
    "allowJs": true,
    "sourceMap": true,
    "outDir": "./output",                        /* Redirect output structure to the directory. */
    "rootDir": "./scripts",
    "removeComments": true, 
    "strict": true,                           /* Enable all strict type-checking options. */
    "noImplicitAny": false,
    "esModuleInterop": true, 
    "skipLibCheck": true,                     /* Skip type checking of declaration files. */
    "forceConsistentCasingInFileNames": true,  /* Disallow inconsistently-cased references to the same file. */
    "experimentalDecorators": true
  }
}

Decorators em classes

./utils/index.ts

export function debug(classe: unknown) {
    console.log("Classe criada!", classe);
}

export function log(constructor: any) {

    return class extends constructor {
        created_at: Date = new Date("2021-02-15")
    }
}

./decorators.ts

import { debug, log } from "./utils";

@debug
class PrimeiraClasse {
    constructor() { }
}

@log
class SegundaClasse {
    constructor() { }
}

console.log(new PrimeiraClasse());
console.log(new SegundaClasse());

Decorators em metódos

function decoratorMetodo(
    target: unknown,
    propertyKey: string,
    descriptor: PropertyDescriptor 
) {

    descriptor.value = (...args: string[]) => {
        return args.map(item => item.toLowerCase());
    };

}

class TratarMensagem {

    @decoratorMetodo
    dizerMensagem(...mensagens: string[]) {
        return mensagens;
    }

}

const instancia = new TratarMensagem();

console.log(instancia.dizerMensagem("Olá", "Seja bem-vindo", "Hcode"));

Decorators em atributos

function decoratorAttr(
    target: unknown,
    nomePropriedade: string
) {

    const novoNome = `_${nomePropriedade}`;
    Object.defineProperty(target, nomePropriedade, {
        get() {
            return this[novoNome].toUpperCase();
        },
        set(novoValor) {
            this[novoNome] = novoValor.split('').reverse().join('');
        }
    })

}

class Animal {

    @decoratorAttr
    nome: string;

    constructor(nome: string) {
        this.nome = nome;
    }
}

const cachorro = new Animal("Pluto");
cachorro.nome = "Snoopy";
console.log(cachorro.nome);

Decorators getter e setters

function decoratorGetSet(valor: boolean) {
    return function(
        target: unknown,
        propertyKey: string,
        descriptor: PropertyDescriptor
    ) {
        descriptor.enumerable = valor;
    }
}

class Login {

    constructor(
        private _usuario: string,
        private _senha: string
    ) {}

    @decoratorGetSet(true)
    get usuario() {
        return this._usuario;
    }

    @decoratorGetSet(false)
    get senha() {
        return this._senha;
    }

}

const login = new Login("[email protected]", "102030");

for (let chave in login) {
    console.log("chave", chave);
    console.log("valor", login[chave]);
}

Decorator em parametros

No tsconfig.ts "emitDecoratorMetadata": true

import "reflect-metadata";

function decoratorParametros(
    target: any,
    key: string,
    propertyKey: number
) {
    return Reflect.getMetadata("design:paramtypes", target, key).map(item => console.log(item));
}

class TratarParametros {    
    metodo1(@decoratorParametros mensagem: string) { }
    metodo2(@decoratorParametros numero: number) { }
}

new TratarParametros();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment