TypeScript - Classes vs Interfaces

TypeScript - Classes vs Interfaces

Neste artigo pretendo apresentar uma comparação entre a utilização de classes e interfaces para a representação do modelo de dados.

  • Introdução
  • Preparando o ambiente de desenvolvimento
  • Construindo os testes
  • Analisando o comportamento com Interfaces
  • Analisando o comportamento com Classes
  • Conclusão

Introdução

TypeScript possui o conceito de classes e interfaces, onde ambos podem ser utilizados para representar o modelo de dados dos nossos objetos. Uma dúvida que encontro bastante em fóruns é a seguinte:

Devo utilizar classes ou interfaces para representar meu modelo de dados?

O objetivo desse artigo é apresentar o veredicto final e esclarecer qualquer dúvida referente ao assunto.

Preparando o ambiente de desenvolvimento

Nessa seção vou descrever os passos que utilizei para preparar o ambiente de desenvolvimento para realizar os testes de comparação.

  • Criando o projeto
$ npm init -y
  • Instalando as dependências de desenvolvimento
$ npm install --save-dev concurrently
$ npm install --save-dev typescript
  • Criando o ponto de entrada

Criei o diretório src e dentro dele adicionei o arquivo index.ts com o seguinte conteúdo.

console.log('It works!');
  • Definindo as configurações de transpilação

Na raiz do projeto, criei o arquivo tsconfig.json com as seguintes configurações.

{
  "compilerOptions": {
    "outDir": "./build",
    "allowJs": true,
    "target": "es5"
  },
  "include": [
    "./src/**/*"
  ]
}

Esse arquivo irá transpilar os arquivos .ts que estão no diretório src para o diretório build.

  • Configurando a transpilação e execução do projeto

Criei o script de inicialização do projeto no arquivo package.json.

...
  "scripts": {
    "start": "concurrently \"tsc -w\" \"nodemon build/index.js\" "
  },
...

O comando tsc -w coloca o compilador em modo watch e a cada alteração nos arquivos do diretório src, o compilador transpila o código para o diretório build automaticamente. O comando nodemon build/index.js executa o projeto. Caso algum arquivo .js seja alterado, o nodemon reinicia a execução.

  • Executando o projeto
$ npm start
  • Validando o ambiente de desenvolvimento

O arquivo index.js deverá ser criado no diretório build e a saída do programa deverá ser It works!.

Construindo os testes

O cenário mais comum para utilizarmos classes ou interfaces é quando fazemos chamadas em API para que os dados sejam exibidos em nosso front-end. Para a execução dos testes vou utilizar a biblioteca node-fetch e a API do Star Wars: https://swapi.co/.

  • Instalando a biblioteca node-fetch
$ npm install node-fetch
  • Construindo a chamada da API
// src/index.ts

import * as fetch from 'node-fetch';

fetch('http://swapi.co/api/people/1')
  .then(response => response.json())
  .then(person => {
    console.log(person.name);
  });

O ponto mais importante a observar no código acima, é que person será implicitamente do tipo any, pois o compilador não tem como identificar seu tipo automaticamente. Como o tipo de person é any, o IntelliSense não é capaz de reconhecer o tipo.

É nesse ponto que precisaremos definir o nosso modelo. E a pergunta que não quer calar:

Devo utilizar classes ou interfaces?

Analisando o comportamento com Interfaces

Para o nosso primeiro teste, iremos construir nosso modelo utilizando interface.

// src/index.ts

import * as fetch from 'node-fetch';

interface Person {
  name: String;
}

fetch('http://swapi.co/api/people/1')
  .then(response => response.json())
  .then((person: Person) => {
    console.log(person.name);
  });

No código acima, estamos definindo explicitamente que o tipo de person é Person. Como o tipo de person está explicitamente definido, o IntelliSense é capaz de reconhecer o tipo.

  • Analisando o resultado da transpilação

Transpilando o código acima para javascript temos o seguinte resultado.

// build/index.js

var fetch = require("node-fetch");
fetch('http://swapi.co/api/people/1')
    .then(function (response) { return response.json(); })
    .then(function (person) {
    console.log(person.name);
});

Analisando o comportamento com Classes

Para o nosso último teste, iremos construir nosso modelo utilizando class.

// src/index.ts

import * as fetch from 'node-fetch';

class Person {
  name: String;
}

fetch('http://swapi.co/api/people/1')
  .then(response => response.json())
  .then((person: Person) => {
    console.log(person.name);
  });

No código acima, estamos definindo explicitamente que o tipo de person é Person. Como o tipo de person está explicitamente definido, o IntelliSense é capaz de reconhecer o tipo.

  • Analisando o resultado da transpilação

Transpilando o código acima para javascript temos o seguinte resultado.

// build/index.js

var fetch = require("node-fetch");
var Person = (function () {
    function Person() {
    }
    return Person;
}());
fetch('http://swapi.co/api/people/1')
    .then(function (response) { return response.json(); })
    .then(function (person) {
    console.log(person.name);
});

WOW 😯😯😯!

Reparem que a classe Person foi transpilada para o código javascript mas não é utilizada em nenhuma parte do código.

Esse código é totalmente inútil.

// build/index.js

...
var Person = (function () {
    function Person() {
    }
    return Person;
}());
...

Conclusão

Acredito que tenha ficado claro qual a melhor forma de representar os modelos de dados. As interfaces irão representar os dados, mas serão completamente removidas durante a transpilação. O grande problema de utilizar classes é a quantidade de código desnecessário que será gerado em um projeto com dezenas de objetos.

Vamos em frente!

"Talk is cheap. Show me the code." - Linus Torvalds
Roberto Achar

Sobre Roberto Achar

Roberto Achar é Full Stack Web Developer e fascinado pelo mundo Open Source. Gosta de escrever sobre Node.js, TypeScript, JavaScript e Angular. Nas horas vagas joga video-game, é marido e pai do Dudu.

Autor no iMasters

Follow @robertoachar

São Paulo, Brasil

Comentários