[Typescript] 🤔 Type ou Interface na hora de tipar?

Fala pessoal,

Venho migrando alguns projetos do commonjs para o typescript e me surgiu uma dúvida sobre a utilização correta ou ao menos a "boa prática" de se usar type e/ou interface na tipagem.

Minha "metodologia"

Exemplo:

type BrasilAPITypes = {
  cnpj: string;
  razao_social: string;
  ddd_telefone_1: string;
};

interface MeusDadosdeSaidaInterface {
    doc: string;
    razao: string;
    telefone: string
}

class TesteBrasilAPI {
    consultaEmpresa(dados: BrasilAPITypes): MeusDadosdeSaidaInterface {
        return {
            doc: dados.cnpj,
            razao: dados.razao_social,
            telefone: dados.ddd_telefone_1
        }
    }
}

Para fins "didáticos" suponhamos que o parametro dados do método consultaEmpresa é populado/alimentado pelo response de algum método qualquer que faça a requisiçao a API e na sequência explico como uso cada caso.

Type

Imagino que uma API externa tem parâmetros imutáveis e nunca antes "tipados" dentro do meu sistema e por isso ao tratar dados recebidos de APIs eu prefiro a utilização do TYPE para que ao consumir o response dela em qualquer parte do meu projeto eu já tenho facilitado o acesso aos parâmetros e tipos que ela possuir.

Interface

Todos os métodos e funções do meu sistema o retorno eu "tipo" com Interface porque além de me pertimitir a implementação direta numa classe, também me permite adicionar novos parâmetros dentro dela caso seja necessário em algum ponto do meu projeto redeclarando ela e passando esses novos parâmetros


O que vocês acham sobre isso? Type ou Interface na tipagem?

Fala @guilzms, tudo tranquilo?

Minha visão sobre Type e Interface

A respeito de qual "modelagem" utilizar em relação ao projeto, no curso que estou fazendo de typescript tive a seguinte visão sobre o uso do alias type e interface:

TYPE: Geralmente utilizado para criar um atalho (alias) para um tipo customizado. Utilizado em tipos primitivos (string, boolean, number...):

type Categorias = 'design' | 'codigo' | 'descod';

function pintarCategoria(categoria: Categorias) {
  if (categoria === 'design') {
    console.log('Pintar vermelho');
  } else if (categoria === 'codigo') {
    console.log('Pintar verde');
  } else if (categoria === 'descod') {
    console.log('Pintar roxo');
  }
}

INTERFACE: Geralmente são utilizadas para definirmos objetos, entretanto não possibilita renomear tipos primitivos.

interface InterfaceProduto {
    nome: string;
    preco: number;
    teclado: number;
}

function preencherDados(dados: InterfaceProduto) {
  document.body.innerHTML += `
  <div>
    <h2>${dados.nome}</h2>
    <p>R$ ${dados.preco}</p>
    <p>Inclui teclado: ${dados.teclado ? 'sim' : 'não'}</p>
  </div>
  `;
}

Um bom ponto de observação, é que quando você cria um alias type,você está criando um apelido e nada mais. O Type é do tipo fechado, onde não é possível criar diferentes "versões" do mesmo tipo, então sua repetição gerará um Erro de TS:

// TYPE
type Window = {
  title: string
}

type Window = {
  ts: TypeScriptAPI
}

 // Error: Duplicate identifier 'Window'.

Para realizar a adição de uma nova propriedade a um Type, seria necessário fazer o seguinte processo:

type TipoCarro = {
  rodas: number;
  portas: number;
};

type TipoCarroComPreco = TipoCarro & {
  preco: number;
};

const dado1: TipoCarroComPreco = {
  preco: 20000,
  rodas: 4,
  portas: 5,
};

Já a Interface é do tipo aberta, então a mesma possibilita adicionar ou alterar campos, e também extende-la apenas redeclarando a mesma :

// Redeclaração de campos
interface InterfaceCarro {
  rodas: number;
  portas: number;
}

interface InterfaceCarro {
  preco: number;
}

const dado2: InterfaceCarro = {
  preco: 20000,
  rodas: 4,
  portas: 5,
};

Conclusão

O Matheus Benites - PapoDe.Dev dá um excelente panorama sobre a utilização de types e Interface:

se precisamos declarar os tipos de uma classe e seus metodos utilizamos interface; se precisamos declarar os tipo das props de uma função usamos alias type; se precisar criar uma definição de tipo que será extendida por algo, nós usamos interface; se estamos criando um generic type para alguma funcionalidade, usamos interface.

Espero ter ajudado, até mais!

Fontes:

Matheus Benites - PapoDe.Dev Documentação - Curso de Typescript - Origamid

Muito boa essa definição! Gostei! Após a leitura do material e das suas conclusões vejo que está parecido com a metodologia que estou aplicando atualmente onde: - Parametros de métodos e funções: Type - Classe, métodos e callbacks: Interface - métodos e callbacks privados não repetidos em outras classes: Type E quando digo sobre métodos repetidos me refiro a isto por exemplo: ```ts interface modeloInterface { dado: string; } class FornecedorUm { consumirApi(): modeloInterface { return { dado: 'oi' } } } class FornecedorDois { consumirApi(): modeloInterface { return { dado: 'tchau' } } } ``` *Nestes exemplos o retorno dado no método `consumirApi` precisa ser identico em todas as classes.* E o legal que vi em vários materiais, seu comentário e também o que estava pensando sobre os **type** é que eles são somente um *alias* enquanto que a **interface**, assim como em outras linguagens, é um contrato que deve ter seus parâmetros e métodos aplicados dentro da classe ou método onde ela é aplicada obrigatóriamente enquanto que o *type* não. Além do exemplo que citei no post original eu poderia implementar esse nível de interfaces também diretamente na classe. ```ts interface EmpresaCallback { doc: string razao: string } type BrasilAPITypes = { cnpj: string; razao_social: string; }; interface GlobalInterface { formataPesquisaCnpj: (param: BrasilAPITypes) => EmpresaCallback } class ConsultaBrasilAPI implements GlobalInterface { formataPesquisaCnpj(callbackApi: BrasilAPITypes): EmpresaCallback { return { doc: callbackApi.cnpj, razao: callbackApi.razao_social } } } ``` Mas essa modelagem no meu caso eu preferi não seguir porque a classe em si é modelo "único" e não vai se repetir enquanto que o meu método `formataPesquisaCnpj` poderá se repetir em outra classe que receberá dados de outra fonte e o callback delas devem ser identicos indiferente da fonte de dados. Além que quero evitar o "interfaces hell" em toda a aplicação assim como existia com o callback hell antes do async/await hahaha :-D Pelo que estou vendo de acordo com os comentários, alguns materiais, troca de experiências e o pouco que tenho de conhecimento é que não existe muito bem denifino do Typescript um "manual de boas práticas" sobre uso de interfaces diferentemente de outras linguagens.

Não sei se existe uma forma "certa" para esse tipo de coisa.

Eu normalmente uso interface da mesma forma que outras linguagens como Java ou PHP, ou seja, mais próximo do escopo de classes mesmo.

Já o type, uso para definir as estrutura dos dados que não tem comportamentos, como DTOs ou parâmetros de funções.

Então para **interface** você usa para a definição das propriedades estáticas e métodos ao invés do retorno e o retorno você aplica o **type**? Exemplo: ```ts type RetornoType = { dado1: string; dado2: number; }; interface TesteInterface { meuMetodo: () => RetornoType; } class Teste implements TesteInterface { meuMetodo(): RetornoType { return { dado1: 'oi', dado2: 123, }; } } ```
Mais ou menos isso mesmo. **interface** para definir comportamentos/contratos, e **type** para definir estruturas.

Não conheço muito bem typescript, mas acredito que este video possa te ajudar:

https://www.youtube.com/watch?v=s9qgTlpYDuA

basicamente, ele mostra 3 principais diferenças entre as formas de tipagem:

Merge declaration 'in'/'keyof' Types com tipos primitivos.

Espero que te ajuda a escolher a melhor forma.

Não sei se ainda é valido, pois ja faz algum tempo que foi gravado.

Assisti esse vídeo pouco antes de ver aqui seu post haha! Muito válida a explicação dele, mas o meu real questionamento é sobre a forma de se aplicar. Existe uma "boa prática"? Qual é a forma que o mundo/vocês estão fazendo?

Costumo usar interface pra tudo, até que um meteoro ameaçe cair na terra caso eu use type. Vide a documentação do TypeScript: