Interfaces - Manual TypeScript Artigo

Conheça os cursos gratuitos do WebDevBr! - Inscreva-se!


Este artigo foi publicado a 11 meses atrás.

Um dos principios fundamentais do TypeScript é o tipo de verificação focada na "forma" dos dados. Este tipo de checagem é chamado de "duck typing" ou "structural subtyping". No TypeScript, interfaces são usadas para ocupar o papel de "dar forma" aos dados e são uma maneira poderosa de definir contratos dentro do seu código, bem como contratos com código fora do seu projeto.

Em algumas linguagens de programação, o termo interface (ou protocolo) é uma referência à característica que permite a construção de interfaces que isolam do mundo exterior os detalhes de implementação de um componente de software.

Fonte: https://pt.wikipedia.org/wiki/Interface_(programa%C3%A7%C3%A3o_orientada_a_objetos)

Gostou deste artigo?

Receba atualizações semanais com novos artigos do WebDevBr e outras dicas!

Os outros artigos desta série.

Nossa primeira interface

A maneira mais simples para entender como as interfaces trabalham é começando com um exemplo simples, sem interface.

    function printLabel(labelledObj: {label: string}) {
        console.log(labelledObj.label);
    }

    var myObj = {size: 10, label: "Size 10 Object"};
    printLabel(myObj);

Note que a função printLabel() recebe um objeto labelledObj que deverá ter um parâmetro label do tipo string, note que o objeto tem mais propriedades, mas o TypeScript verifica apenas o que foi solicitado.

Podemos reescrever este exemplo novamente, mas desta vez usando uma interface para descrever as exigências necessárias.

    interface LabelledValue {
        label: string;
    }

    function printLabel(labelledObj: LabelledValue) {
        console.log(labelledObj.label);
    }

    var myObj = {size: 10, label: "Size 10 Object"};
    printLabel(myObj);

A interface LabelledValue é o nome que usamos para descrever as exigências do exemplo anterior. Ainda exige que uma propriedade label do tipo string exista. Note que eu não precisei informar que myObj implemente a interface LabelledValue como acontece em outras linguagens, apenas preciso que atenda aos requisitos, o importante aqui é a "forma".

Vale ressaltar que a ordem também não importa muito, apenas que as propriedades informadas pela interface existam e seja do tipo especificado.

Propriedades opcionais

Um detalha legal sobre interfaces no TypeScript é que nem sempre uma propriedade é obrigatória, ela pode ser opcional, por exemplo, quando você quer criar um "options" e alguns valores serão preenchidos apenas. Por exemplo:

    interface SquareConfig {
        color?: string;
        width?: number;
    }

    function createSquare(config: SquareConfig): {color: string; area: number} {
        var newSquare = {color: "white", area: 100};
        if (config.color) {
            newSquare.color = config.color;
        }
        if (config.width) {
            newSquare.area = config.width * config.width;
        }
        return newSquare;
    }

    var mySquare = createSquare({color: "black"});

As propriedades opcionais são escritas como as obrigatórias, porém com uma interrogação (?) no final do nome, você ainda pode passar o tipo, desta forma, caso a propriedade seja preenchida, só será aceito aquele formato.

Outro ponto importante é que, caso você informe uma propriedade que não deveria estar disponível ele ainda vai retornar um erro pra você.

    interface SquareConfig {
        color?: string;
        width?: number;
    }

    function createSquare(config: SquareConfig): {color: string; area: number} {
        var newSquare = {color: "white", area: 100};
        if (config.color) {
            newSquare.color = config.collor;  // Type-checker vai informar o nome errado aqui
        }
        if (config.width) {
            newSquare.area = config.width * config.width;
        }
        return newSquare;
    }

    var mySquare = createSquare({color: "black"});  

Tipos para funções

E agora a coisa começa a ficar bacana, além de informar os tipos que objetos podem aceitar, você também pode definir quais parametros e os tipos que uma função terá, e, claro, o que o método deve retornar.

Para descrever tipos para funções no TypeScript, nos fazemos como a chamada de um método, assim:

    interface SearchFunc {
        (source: string, subString: string): boolean;
    }

Ou seja, eu passo as propriedades, os tipos e o que deve ser retornado, como faria com um método, mas sem nome e sem corpo, apenas a assinatura da função.

Agora que tenho uma interface eu posso usar para definir os tipos da variável e forçar que ela tenha o formato esperado:

    var mySearch: SearchFunc;
    mySearch = function(source: string, subString: string) {
        var result = source.search(subString);
        if (result == -1) {
            return false;
        }
        else {
            return true;
        }
    }

O nome dos parâmetros não precisam corresponder exatamente o que está definido na interface, eu poderia, por exemplo, fazer assim:

    var mySearch: SearchFunc;
    mySearch = function(src: string, sub: string) {
        var result = src.search(sub);
        if (result == -1) {
            return false;
        }
        else {
            return true;
        }
    }

Também funciona.

Arrays

Interfaces para definir arrays também são bem legais, podemos definir o tipo do indice o tipo do valor:

    interface StringArray {
        [index: number]: string;
    }

    var myArray: StringArray;
    myArray = ["Bob", "Fred"];

Dois tipos de indices são suportados: string e number.

Enquanto assinaturas de indice são uma maneira poderosa para descrever o array, eles também impõem que todas as propriedades devem coincidir com o seu tipo de retorno. Neste exemplo, a propriedade não corresponde ao índice e gera um erro.

    interface Dictionary {
        [index: string]: string;
        length: number;    // error, the type of 'length' is not a subtype of the indexer
    } 

Classes implementanto interfaces

Como em outras linguagens, como PHP, C# ou Java, também é possível fazer com que classes implementem interfaces, na verdade este é o uso mais comum das interfaces.

    interface ClockInterface {
            currentTime: Date;
    }

    class Clock implements ClockInterface  {
            currentTime: Date;
            constructor(h: number, m: number) { }
    }

Da pra informar métodos a serem implementados:

    interface ClockInterface {
            currentTime: Date;
            setTime(d: Date);
    }

    class Clock implements ClockInterface  {
            currentTime: Date;
            setTime(d: Date) {
                    this.currentTime = d;
            }
            constructor(h: number, m: number) { }
    }

Mas ainda temos um detalhe, quando definimos o contrutor em uma interface, não é possível implementar a interface direto na classe, isso porque quando uma classe implementa uma interface, apenas a instância é verificada, o construtor fica do lado estático.

    interface ClockInterface {
            new (hour: number, minute: number);
    }

    class Clock implements ClockInterface  {
            currentTime: Date;
            constructor(h: number, m: number) { }
    }

O exemplo acima não funciona por causa do construtor definido na interface (através da palavra new), o correto é você implementar a interface na variável que vai receber a instância da classe:

    interface ClockStatic {
            new (hour: number, minute: number);
    }

    class Clock  {
            currentTime: Date;
            constructor(h: number, m: number) { }
    }

    var cs: ClockStatic = Clock;
    var newClock = new cs(7, 30);

E como é esperado, as interfaces descrevem atributos e métodos públicos, não é possível implementar qualquer coisa privada.

Estendendo interfaces

Assim como as classes, interfaces podem estender outras interfaces, assim você pode segrega-las (e seguir o I do SOLID), assim mantendo uma estrutura mais enxuta e organizada.

    interface Shape {
            color: string;
    }

    interface PenStroke {
            penWidth: number;
    }

    interface Square extends Shape, PenStroke {
            sideLength: number;
    }

    var square = <Square>{};
    square.color = "blue";
    square.sideLength = 10;
    square.penWidth = 5.0;

Tipos hibridos

Por conta da natureza dinâmica do JavaScript, você pode encontrar objetos que trabalham com uma combinação dos tipos informados acima.

Veja um exemplo de objeto que atua como função e como objeto:

    interface Counter {
            (start: number): string;
            interval: number;
            reset(): void;
    }

    var c: Counter;
    c(10);
    c.reset();
    c.interval = 5.0;

Conclusão

Este é o segundo artigo da série Manual do TypeScript, ele não vai abordar um projeto prático, mas serve como um guia de consulta rápida pra você que está começando ou que já trabalha com isso a algum tempo.


Cursos relacionados


* Parcelamento apenas cartão de crédito! Pode haver uma pequena variação no parcelamento em relação a simulações apresentadas!