Uma interface é uma forma de um objeto. Um objeto JavaScript padrão é um mapa de key:value pares. JavaScript object keys em quase todos os casos são strings e seus valores são quaisquer valores JavaScript suportados (primitivo ou abstrato).

Uma interface diz ao compilador TypeScript sobre nomes de propriedades que um objeto pode ter e seus tipos de valores correspondentes. Portanto, interface é um tipo e é um tipo abstrato pois é composto de tipos primitivos.

Quando definimos um objeto com propriedades (chaves) e valores, TypeScript cria uma interface implícita olhando os nomes das propriedades e o tipo de dados de seus valores no objeto. Isto acontece por causa do tipo inference.

(object-shape.ts)

No exemplo acima, criamos um objeto student com campos firstName, lastName, age e getSalary e atribuímos alguns valores iniciais. Usando esta informação, TypeScript cria um tipo de interface implícita para student.

{
firstName: string;
lastName: string;
age: number;
getSalary: (base: number) => number;
}

Uma interface é como um objeto, mas contém apenas a informação sobre as propriedades do objeto e seus tipos. Podemos também criar um tipo de interface e dar-lhe um nome para que possamos usá-lo para anotar os valores dos objetos, mas aqui, esta interface não tem um nome, uma vez que foi criada implicitamente. Você pode comparar isso com o tipo de função da lição anterior que foi criada implicitamente no início e então criamos um tipo de função explicitamente usando o tipo alias.

Vamos tentar mexer nas propriedades do objeto depois que ele foi definido.

(shape-override.ts)

Como você pode ver no exemplo acima, TypeScript lembra a forma de um objeto uma vez que o tipo de ross é a interface implícita. Se tentarmos sobrepor o valor de uma propriedade com um valor de tipo diferente do especificado na interface ou tentarmos adicionar uma nova propriedade que não esteja especificada na interface, o compilador TypeScript não compilará o programa.

Se você quiser que um objeto tenha basicamente qualquer propriedade, então você pode marcar explicitamente um valor any e o compilador TypeScript não irá inferir o tipo a partir do valor do objeto atribuído. Existem outras formas melhores de conseguir exatamente isso e nós vamos passar por elas neste artigo.

(shape-override-any.ts)

Interface Declaration

Pois a interface implícita que vimos até agora é tecnicamente um tipo, mas não foi definida explicitamente. Como discutido, uma interface não é nada além da forma que um objeto pode tomar. Se você tem uma função que aceita um argumento que deveria ser um objeto mas de uma forma particular, então precisamos anotar esse argumento (parâmetro) com um tipo de interface.

(argument-with-shape.ts)

No exemplo acima, definimos uma função getPersonInfo que aceita um argumento objeto que tem firstName, lastName, age e getSalary campos de tipos de dados especificados. Note que usamos um objeto que contém nomes de propriedades e seus tipos correspondentes como um tipo usando :<type> anotação. Este é um exemplo de uma interface anônima uma vez que a interface não tem nome, ela foi usada inline.

Isso tudo parece um pouco complicado de se lidar. Se o objeto ross ficar mais complicado e precisar ser usado em vários lugares, o TypeScript parece apenas uma coisa que você gostou inicialmente, mas agora apenas uma coisa difícil de se lidar. Para resolver este problema, definimos um tipo de interface usando interface palavra-chave.

(argument-with-interface.ts)

No exemplo acima, definimos uma interface Person que descreve a forma de um objeto, mas desta vez, temos um nome que podemos usar para nos referirmos a este tipo. Usamos este tipo para anotar ross variável assim como o argumento person da função getPersonIfo. Isto irá informar o TypeScript para validar estas entidades contra a forma de Person.

Porquê usar uma interface?

O tipo de interface pode ser importante para impor uma determinada forma. Tipicamente em JavaScript, nós colocamos fé cega em tempo de execução que um objeto sempre conterá uma propriedade particular e que a propriedade sempre terá um valor de um tipo particular como {age: 21, ...} como exemplo.

Quando realmente começamos a executar operações naquela propriedade sem primeiro verificar se aquela propriedade existe no objeto ou se seu valor é o que esperávamos, as coisas podem dar errado e pode deixar a sua aplicação inutilizável depois. Por exemplo, {age: '21', ...}, aqui age o valor é um string.

Interfaces fornecem um mecanismo seguro para lidar com tais cenários em tempo de compilação. Se você estiver usando acidentalmente uma propriedade em um objeto que não existe ou usando o valor de uma propriedade na operação ilegal, o compilador TypeScript não compilará seu programa. Vejamos um exemplo.

(interface-safety.ts)

No exemplo acima, estamos tentando usar name propriedade do argumento _student dentro da função printStudent. Já que o argumento _student é um tipo de interface de Student, o compilador TypeScript lança um erro durante a compilação, já que esta propriedade não existe na interface de Student.

Similiarmente, 100 — _student.firstName não é uma operação válida uma vez que firstName a propriedade é um tipo de string e da última vez que verifiquei, não se pode subtrair um string de um number é JavaScript (resulta em NaN).

No exemplo acima, usamos a forma tradicional de escrever o tipo de função para o campo getSalary. No entanto, você também pode usar a sintaxe da função sem o corpo para o mesmo, que é geralmente usada em interfaces.

interface Student {
firstName: string;
lastName: string;
age: number;
getSalary(base: number): number;
};

Propriedades Opcionais

Por vezes, você precisa de um objeto para ter uma propriedade que contenha dados de um determinado tipo de dados, mas não é obrigatório ter essa propriedade no objeto. Isto é similar aos parâmetros de funções opcionais que aprendemos na lição anterior.

Suas propriedades são chamadas propriedades opcionais. Uma interface pode conter propriedades opcionais e nós usamos ?:Type anotação para representá-las, assim como os parâmetros da função opcional.

(optional-properties.ts)

No exemplo acima, a interface Student tem a propriedade age que é opcional. Entretanto, se a propriedade age for fornecida, ela deve ter um valor do tipo number.

No caso do objeto ross que é um tipo de interface Student, não fornecemos o valor para a propriedade age que é legal, entretanto, no caso de monica, fornecemos a propriedade age mas seu valor é string que não é legal. Daí o compilador TypeScript lançar um erro.

O erro pode parecer estranho, mas na verdade faz sentido. Se a propriedade age não existe em um objeto, a object.age retornará undefined que é um tipo de undefined. Se ela existe, então o valor deve ser do tipo number.

Atenha o valor da propriedade age pode ser do tipo undefined ou number que no TypeScript é representado usando a sintaxe union number | undefined.

💡 Vamos aprender uniões de tipos em uma lição de Type System.

No entanto, propriedades opcionais colocam sérios problemas durante a execução do programa. Vamos imaginar se estamos usando age propriedade em uma operação aritmética, mas seu valor é undefined. Este é um tipo de problema sério.

Mas o bom é que o compilador TypeScript não permite realizar operações ilegais em uma propriedade opcional, pois seu valor pode ser undefined.

(optional-properties-safety.ts)

No exemplo acima, estamos realizando uma operação aritmética em age propriedade que é ilegal porque o valor desta propriedade pode ser number ou undefined no tempo de execução. Executando operações aritméticas em undefined resulta em NaN (não um número).

💡 No entanto, para o programa acima, tivemos tp definido --strictNullChecks flag para false que é uma flag do compilador TypeScript. Se fornecemos esta opção, o programa acima compila muito bem.

Para evitar este erro ou aviso, precisamos dizer explicitamente ao compilador TypeScript que esta propriedade é um tipo de number e não o number ou undefined. Para isso, usamos a afirmação do tipo (conversão do tipo AKA ou digitação).

(optional-properties-safety-override.ts)

No programa acima, usamos (_student.age as number) que converte o tipo de _student.age de number | undefined para number. Esta é uma maneira de dizer ao compilador TypeScript, “Ei, isto é um número”. Mas uma maneira melhor de lidar com isto seria também verificar se _student.age é undefined em tempo de execução e então executar a operação aritmética.

💡 Vamos aprender sobre asserções de tipo em uma lição de Sistema de Tipo.

Tipo de função usando uma interface

Não apenas a forma de um objeto simples, mas uma interface também pode descrever a assinatura de uma função. Na lição anterior, usamos o tipo alias para descrever um tipo de função, mas interfaces também podem fazer isso.

interface InterfaceName {
(param: Type): Type;
}

A sintaxe para declarar uma interface como um tipo de função é similar à assinatura da própria função. Como você pode ver no exemplo acima, o corpo da interface contém a assinatura exata de uma função anônima, sem o corpo da função, é claro. Aqui os nomes dos parâmetros não importam.

(function-signature.ts)

No exemplo acima, definimos IsSumOdd interface que define um tipo de função que aceita dois argumentos do tipo number e retorna um valor de boolean. Agora você pode usar este tipo para descrever uma função porque o tipo de interface IsSumOdd é equivalente ao tipo de função (x: number, y: number) => boolean.

Uma interface com uma assinatura de método anônima descreve uma função. Mas uma função no reino JavaScript também é um objeto, o que significa que você pode adicionar propriedades a um valor de função, assim como um objeto. Portanto, é perfeitamente legal que você possa definir quaisquer propriedades em uma interface do tipo função.

(function-signature-with-methods.ts)

No exemplo acima, adicionamos type e calculate propriedades na interface IsSumOdd que descreve uma função. Usando o método Object.assign, estamos fundindo type e calculate propriedades com um valor de função.

Interfaces do tipo de função podem ser úteis para descrever funções de construtor. Uma função construtora é similar a uma classe cujo trabalho é criar objetos (instâncias). Nós só tivemos funções de construtor até ES5 para imitar um class em JavaScript. Portanto, o TypeScript compila classes para funções de construtor se você estiver visando ES5 ou abaixo.

💡 Se você quiser aprender mais sobre funções de construtor, siga este artigo.

Se colocarmos new palavra-chave antes da assinatura anônima da função na interface, isso torna a função construtível. Isso significa que a função só pode ser invocada usando new palavra-chave para gerar objetos e não usando uma chamada de função regular. Um exemplo de função construtora se parece com abaixo.

function Animal( _name ) {
this.name = _name;
}var dog = new Animal( 'Tommy' );
console.log( dog.name ); // Tommy

Felizmente, não precisamos trabalhar com funções construtoras já que o TypeScript fornece class keyword para criar uma classe que é muito mais fácil de trabalhar do que uma função construtora, confie em mim. Na verdade, um class no fundo é uma função construtora em JavaScript. Tente o exemplo abaixo.

class Animal{
constructor( _name ) {
this.name = _name;
}
}console.log( typeof Animal ); // "function"

Uma classe e uma função construtora são uma e a mesma coisa. A única diferença é que o class nos dá uma sintaxe OOP rica para trabalhar. Portanto, uma interface de um tipo de função construtora representa uma classe.

(function-signature-with-new.ts)

No exemplo acima, definimos a classe Animal com uma função construtora que aceita um argumento do tipo string. Você pode considerar isto como uma função construtora que tem uma assinatura semelhante a Animal construtor.

O AnimalInterface define uma função construtora uma vez que tem uma função anônima prefixada com a palavra-chave new. Isto significa que a classe Animal qualifica para ser um tipo de AnimalInterface. Aqui, AnimalInterface tipo de interface é equivalente ao tipo de função new (sound: string) => any.

a função createAnimal aceita ctor argumento de AnimalInterface tipo, portanto podemos passar Animal classe como o valor do argumento. Não seremos capazes de adicionar getSound método assinatura da Animal classe em AnimalInterface e o motivo é explicado na lição Classes.

Indexable Types

Um objeto indexável é um objeto cujas propriedades podem ser acessadas usando uma assinatura de índice como obj. Esta é a forma padrão de acessar um elemento de array mas também podemos fazer isso para o objeto.

var a = ;
var o = { one: 1, two: 2, three: 3 };console.log( a ); // 1
console.log( a ); // 1 (same as `a.one`)

Por vezes, seu objeto pode ter um número arbitrário de propriedades sem qualquer forma definida. Nesse caso, você pode apenas usar object tipo. Entretanto, este object type define qualquer valor que não number, string, boolean, symbol, null, ou undefined como discutido na lição básica de tipos.

Se precisarmos verificar estritamente se um valor é um objeto JavaScript simples, então poderemos ter um problema. Isto pode ser resolvido usando um tipo de interface com uma assinatura de índice para o nome da propriedade.

interface SimpleObject {
: any;
}

A interface SimpleObject define a forma de um objeto com chaves string cujos valores podem ser any tipo de dado. Aqui, o nome da propriedade key é apenas usado para placeholder já que está entre parênteses rectos.

(indexable-type-object.ts)

No exemplo acima, definimos ross e monica objecto do tipo SimpleObject interface. Como esses objetos contêm string chaves e valores de any tipo de dado, é perfeitamente legal.

Se você estiver confuso sobre a chave 1 no monica que é um tipo de number, isso é legal já que os itens do objeto ou array em JavaScript podem ser indexados usando chaves number ou string, como mostrado abaixo.

var o = { 0: 'Zero', '1': 'One' };
var a = ;console.log( o ); // Zero
console.log( o ); // One
console.log( a ); // One
console.log( a ); // One

Se precisarmos ser mais precisos sobre o tipo de chaves e seus valores, certamente podemos fazer isso também. Por exemplo, podemos definir um tipo de interface indexável com chaves do tipo number e valores do tipo number se quisermos.

💡 Um tipo de chave de assinatura de índice deve ser ou string ou number.

(indexable-type-number.ts)

No exemplo acima, definimos uma interface LapTimes que pode conter nomes de propriedades do tipo number e valores do tipo number. Esta interface pode representar uma estrutura de dados que pode ser indexada usando number chaves, portanto, array ross e objetos monica e joey são legais.

No entanto, o objeto rachel não está de acordo com a forma de LapTimes uma vez que a chave one é um string e só pode ser acessado usando string como rachel e nada mais. Assim o compilador TypeScript irá lançar um erro como mostrado acima.

É possível ter algumas propriedades necessárias e algumas opcionais em um tipo de interface indexável. Isto pode ser bastante útil quando precisamos de um objeto para ter uma determinada forma, mas realmente não importa se obtemos propriedades extras e indesejadas no objeto.

(indexable-type-required-properties.ts)

No exemplo acima, definimos uma interface LapTimes que deve conter propriedade name com string valor e propriedade opcional age com number valor. Um objeto do tipo LapTimes também pode ter propriedades arbitrárias cujas chaves devem ser number e cujos valores também devem ser number.

O objeto ross é um objeto válido LapTimes mesmo que não tenha age propriedade já que é opcional. No entanto, monica tem a propriedade age mas o seu valor é string por isso não cumpre com a interface LapTimes.

O objecto joey também não cumpre com a interface LapTimes pois tem uma propriedade gender que é um tipo de string. O objeto rachel não tem name propriedade que é requerida na LapTimes interface.

💡 Há algumas gotchas que precisamos procurar enquanto usamos tipos indexáveis. Estes são mencionados nesta documentação.

Interface de extensão

Classes, uma interface pode herdar propriedades de outras interfaces. No entanto, ao contrário das classes em JavaScript, uma interface pode herdar de múltiplas interfaces. Usamos extends palavra-chave para herdar uma interface.

(extend-interface.ts)

Ao estender uma interface, a interface filho obtém todas as propriedades da interface pai. Isto é bastante útil quando múltiplas interfaces têm uma estrutura comum e queremos evitar a duplicação de código, retirando as propriedades comuns para uma interface comum que pode ser herdada mais tarde.

(extend-multiple-interface.ts).ts)

No exemplo acima, criamos uma interface Student que herda as propriedades da interface Person e Player interface.

Declarações de interfaces múltiplas

Na seção anterior, aprendemos como uma interface pode herdar as propriedades de outra interface. Isto foi feito usando a palavra-chave extend. Entretanto, quando interfaces com o mesmo nome são declaradas dentro do mesmo módulo (arquivo), o TypeScript funde suas propriedades desde que elas tenham nomes de propriedades distintos ou seus tipos de propriedades conflitantes sejam os mesmos.

(merged-interface.ts)

No exemplo acima, declaramos Person interface várias vezes. Isso resultará em uma única declaração de interface Person fundindo as propriedades de todas as declarações de interface Person.

💡 Na lição Classes, aprendemos que um class declara implicitamente uma interface e uma interface pode estender essa interface. Então se um programa tem uma classe Person e uma interface Person, então o final Person tipo (interface) terá propriedades fundidas entre a classe e a interface.

Interfaces Internas

Uma interface pode ter estruturas profundamente aninhadas. No exemplo abaixo, o campo info da interface Student define a forma de um objeto com firstName e lastName propriedades.

(nested-shape.ts)

Likewise, é perfeitamente legal que um campo de uma interface tenha o tipo de outra interface. No exemplo a seguir, o campo info da interface Student tem o tipo de Person interface.

(nested-interface.ts)

Deixe uma resposta

O seu endereço de email não será publicado.