(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)