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.
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.
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
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
.
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
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);
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 é 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()
}
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:
- Herança
- Encapsulamento
- Polimorfismo
- Abstração
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);
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 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:
- public
- protected
- private
- readonly (apenas atributo)
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());
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());
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);
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);
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);
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);
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);
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);
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());
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"
});
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();
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
}
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:
- Melhor organização do código
- Melhor reutilização do código
- 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"
});
./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';
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);
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 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:
- Funções
- Interfaces
- Classes
function obterPrimeiraPosicao<T>(meuArray: Array<T>): T | undefined {
return meuArray.pop();
}
console.log(obterPrimeiraPosicao(["Glaucio", 10, "Rafael", "Ronaldo"]));
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));
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));
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 é 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:
- Parâmetros
- Classes
- Atributos
- Métodos
- 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
}
}
./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());
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"));
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);
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]);
}
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();