Diferenças entre Types e Interfaces no Typescript.

Diferenças entre Types e Interfaces no Typescript.

O TypeScript continua avançando e crescendo, como novos recursos implementados a cada ano. Se você ainda trabalha com Javascript, está perdendo muitos recursos interessantes e úteis, que tem facilitado o dia a dia de muitos desenvolvedores! Mas o foco neste artigo é apresentar com clareza as diferenças entre uma interface e types alias. Vamos utilizar para demonstração códigos simples, além de utilizar o próprio playground do TypeScript, pois assim você pode rapidamente testar e entender na prática como tudo realmente se comporta. Vamos lá!

Types

No typescript temos muitos tipos, e alguns deles são numbers, string, Date, boolean, enum entre outros. Esses são considerados tipos básicos da linguagem. Mas além disso, no TypeScript, temos tipos avançados e nesses tipos, temos type aliases. Com aliases, podemos criar um novo nome para um tipo, mas não definimos um novo tipo. Por isso que alguns desenvolvedores podem ficar confusos e acabam pensando que estão criando um novo tipo quando estão apenas criando um novo nome para um tipo. Isso está descrito na própria documentação oficial do Typescript.

Certo tivemos uma breve introdução para compreender o que envolve um Type. Vamos partir para as diferenças.

Interface e Type

A diferença entre tipos e interfaces no TypeScript costumava ser mais clara, mas com as versões mais recentes da linguagem, elas estão se tornando um pouco semelhantes.

As Interfaces continuam a ser contratos, são basicamente uma maneira de descrever formas de dados, por exemplo, um objeto.

Types é uma definição de um tipo de dado, por exemplo, uma união, primitiva, interseção, tupla ou qualquer outro tipo.

A documentação apresenta essa seguinte explicação:

" a principal distinção é que um type não pode ser reaberto para adicionar novas propriedades, mas uma interface é sempre extensível. " - Typescript Doc

Vamos entender isso mais claramente!

Mapped Types

Um dos recursos mais interessantes na minha opinião é o mapped types. Com este recurso podemos fazer o mapeamento de um tipo para o outro. Veja um exemplo abaixo:

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

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

const stringPerson: Stringify<Person> = {
name: 'Rafael',
age: '25'
}

Originalmente na interface Persona propriedade age era number. Mas com este recurso se tornou uma string, depois de passar pelo type Stringify. E se passarmos o Stringify para uma interface? Veja o erro que recebemos:

Captura de Tela 2022-04-05 às 10.17.35.png

O compilador já nos alerta que isso não é uma sintaxe válida utilizando uma interface!

Mas atenção! O tipo genérico <T> sempre será valido, o que não está em conformidade com interfaces é a declaração [P in keyof T].

Merge

Uma coisa que é possível fazer com interfaces, mas não com types, é a mesclagem de declarações. A mesclagem de declaração acontece quando o compilador do TypeScript mescla (realiza o merge) de duas ou mais interfaces que compartilham o mesmo nome em apenas uma declaração.

interface Cliente {
  cpf: string
}

interface Cliente {
  cnpj: string
}

const meuCliente: Cliente = {
cpf: '1234.766.999-0',
cnpj: '82.507.350/0001-03'
}

Se você colocar este código acima verá quer é possível mesclar as duas declarações de interface em uma, portanto, quando usarmos essa interface Cliente, teremos as duas propriedades.

Legal, mas isso é possível com types? Infelizmente não. O merge de declaração não funciona com type. Se tentarmos criar dois tipos com os mesmos nomes, mas com propriedades diferentes, mesmo assim o TypeScript ainda nos lançará um erro.

Duplicate identifier Cliente.

Implementar e extender

Se você trabalha alguns meses com typescript sabe que podemos facilmente estender e implementar interfaces. Isso não é possível com type.

Com este recurso que as interfaces no proporcionam como estender classes, também podemos criar classes implementando interfaces. Tudo depende da necessidade para alcançar seu objetivo.

interface IPerson {
    name: string;
    gender: string;
}

interface IEmployee extends IPerson {
    empCode: number;
}

let empObj:IEmployee = {
    empCode:1,
    name:"Bill",
    gender:"Male"
}

// Classe implementando interface 

interface IEmployee {
    empCode: number;
    name: string;
    getSalary:(empCode: number) => number;
}

class Employee implements IEmployee { 
    empCode: number;
    name: string;

    constructor(code: number, name: string) { 
        this.empCode = code;
        this.name = name;
    }

    getSalary(empCode:number):number { 
        return 20000;
    }
}

Interseção

A interseção nos permite combinar vários tipos em um único tipo. Para criar um typede interseção, temos que usar a palavra reservada &:

type Name = {
  name: “string”
};

type Age = {
  age: number
};

type Person = Name & Age;

O legal é que podemos criar um novo tipo de interseção combinando duas interfaces, mas não o contrário. Não podemos criar uma interface combinando dois tipos, pois não funciona:

interface Name {
  name: “string”
};

interface Age {
  age: number
};

type Person = Name & Age;

Unions

Unions types nos permitem criar um novo tipo que pode ter o valor de um ou mais tipos. Para criar um union type, temos que usar o | (pipe):

type Man = {
  name: “string”
};

type Woman = {
  name: “string”
};

type Person = Man | Woman;

Semelhante às interseções, podemos criar um novo tipo de union combinando duas interfaces, por exemplo, mas novamente não o contrário:

interface Man {
  name: "string"
};

interface Woman {
  name: "string"
};

type Person = Man | Woman;

Tuples

Tuplas são um conceito muito útil da linguagem que nos trouxe esse novo tipo de dados que inclui dois conjuntos de valores de diferentes tipos de dados.

Mas, no TypeScript, só podemos declarar tuplas usando types e não interfaces. Não há como declarar uma tupla no TypeScript usando uma interface. Mas você ainda pode usar uma tupla dentro de uma interface, dessa maneira:

interface Response {
  value: [string, number]
}

// type tuple

type Reponse = [string, number]

Podemos obter o mesmo resultado usando tipos com interfaces. Então, agora vem a pergunta que muitos desenvolvedores podem ter - devo usar um type em vez de uma interface? Se sim, quando devo usar?

Vamos analisar essas perguntas com calma!

Quando utilizar Types e Interfaces?

Essa pergunta é realmente complicada. E tudo vai depender do que você está construindo e no que está trabalhando. É importante analisar seu contexto e projeto atual.

As interfaces são melhores quando você precisa definir um novo objeto ou método de um objeto.

Para desenvolvedores que trabalham com React, sabem que quando precisam definir as props que um componente específico vai receber, é ideal usar interface sobre types:

interface TodoProps {
  name: string;
  isCompleted: boolean
};

const Todo: React.FC<TodoProps> = ({ name, isCompleted }) => {
  ...
};

Alguns programadores utilizam types quando precisam criar funções, por exemplo, vamos imaginar que temos uma função que vai retornar um objeto chamado, utilizar type alias é mais recomendado para esta abordagem:

type Person = {
  name: string,
  age: number
};

type ReturnPerson = (
  person: Person
) => Person;

const returnPerson: ReturnPerson = (person) => {
  return person;
};

Você não deve começar a usar um e excluir o outro. Em vez de fazer isso, comece a refatorar lentamente, pensando no que faz mais sentido para aquela situação específica.

Outro alternativa de como utilizar. Você pode definir interfaces para métodos:

code22.png

E para seus tipos de dados, dependendo obviamente da implementação, podemos utilizar types dessa maneira:

code228.png

Conclusão

Lembre-se de que você pode usar os dois juntos e eles funcionarão bem. A ideia aqui é apenas esclarecer as diferenças entre types e interfaces e os melhores casos de uso para ambos.

Fico por aqui neste post, agradeço muito por ler até aqui! Até o próximo! 😉