Перейти к основному содержимому

Интерфейсы

Одним из основных принципов TypeScript является то, что типизация основана на структуре объектов. Такой способ типизации называют неявной или «утиной» — объект относят к тому или иному типу (классу, интерфейсу), если он имеет (реализует) все его свойства и методы. Интерфейсы в TS применяются как раз для того, чтобы описывать нужные вам типы.

Utka

Простой пример

Лучший способ узнать, как работают интерфейсы - начасть с простого примера:

Пример

let giveFruit = (fruit: { name: string }) => console.log('Give to me ' + fruit.name)

let myFruits = { name: 'Banana', sweetness: 7, bones: false }
giveFruit(myFruits)

Функция giveFruit() имеет единственный параметр, который требует, чтобы переданный объект имел свойство с именем name типа string. Обратите внимание, что наш объект на самом деле имеет больше свойств, чем требуется, но компилятор только проверяет, присутствуют ли хотя бы те, которые необходимы, и соответствуют требуемым типам.

Напишем тот же пример, для проверки свойства name с типом string, но при помощи интерфейсов.

Пример

interface Fruit {
name: string
sweetness: number
bones: boolean
}

let giveFruit = (fruit: Fruit) => console.log('Give to me ' + fruit.name)

let myFruits = { name: 'Banana', sweetness: 7, bones: false }
giveFruit(myFruits)

Интерфейс Fruit - это имя, которое мы теперь можем использовать для описания требования в предыдущем примере. Обратите внимание, что нам не нужно было явно указывать, что объект, который мы передаем в функцию giveFruit(), наследует этот интерфейс, как это может быть в других языках. Здесь важен только образец. Если объект, который мы передаем функции, соответствует перечисленным требованиям, то всё позволено.

Стоит отметить, что проверка типов не требует, чтобы эти свойства имели какой-либо порядок, а только то, что свойства, необходимые для интерфейса, присутствуют и имеют требуемый тип.

Interface

Необязательные свойства

Не все свойства интерфейса могут быть обязательными. Некоторые существуют при определенных условиях или могут вообще отсутствовать. Интерфейсы с необязательными свойствами записываются аналогично другим интерфейсам, где каждое необязательное свойство обозначается знаком ? в конце имени свойства в декларации.

Пример

interface Fruit {
name: string
sweetness: number
bones: boolean
color?: number
}

let banana: Fruit = {
name: 'Banana',
sweetness: 7,
bones: false,
color: 0xffe135
}

let apple: Fruit = {
name: 'Apple',
sweetness: 5,
bones: true
}

Необязательные свойства популярны при создании шаблонов, таких как «option bags», в которых вы передаете объект в функцию, у которого заполнены только пара свойств.

Questions

Только для чтения

Некоторые свойства могут быть заданы только для чтения, а значение они получат при создании объекта. Этого можно добиться, поместив ключевое слово readonly перед именем свойства.

Пример

interface Point {
readonly x: number;
readonly y: number;
}

let a1: Point = { x: 10, y: 40 }

console.log('Точка [' + a1.x + '; ' + a1.y + ']')

Можно создать переменную c типом Point, присвоив ей литерал объекта. После этого значения свойств x и y изменять будет нельзя.

readonly

Лишние свойства

В нашем первом примере использования интерфейсов TypeScript позволил передать { name: string; sweetness: number, bones: boolean } там, где ожидалось всего лишь { name: string }. Также мы узнали о необязательных свойствах, и о том, как они могут быть полезны при передаче аргументов в функции. Рассмотрим пример.

Пример

interface Fruit {
name: string
sweetness?: number
bones?: boolean
color?: number
}

function addFruit(x: Fruit): { name: string; color: number } {
// ...
}

let banana = addFruit({ name: 'banana', colour: 0xffe135 })
// error: 'colour' not expected in type 'Fruit'

Обратите внимание, что аргумент, передаваемый в addFruit(), записан как colour вместо color. В чистом JavaScript подобные вещи не выдают ошибок, но и не работают так, как хотел бы разработчик.

Можно сказать, что данная программа корректна с точки зрения типов, так как типы свойств sweetness совместимы, color отсутствует, а наличие дополнительного свойства colour не имеет никакого значения.

Однако TypeScript делает предположение, что в этом куске кода есть ошибка. Литералы объектов обрабатываются им по-особенному, и проходят проверку на наличие лишних свойств. Эта проверка делается, когда литералы либо присваиваются другим переменным, либо передаются в качестве аргументов. Если в литерале есть какие-либо свойства, которых нет в целевом типе, то это будет считаться ошибкой.

Обойти такую ошибку можно несколькими способами.

Первый способ

One

Использование приведение типов:

Пример

let banana = addFruit({ name: 'banana', colour: 0xFFE135 } as Fruit)

Второй способ

two

Добавление строкового индекса, его лучше использовать тогда, когда вы уверены, что объект может иметь дополнительные свойства.

Пример

interface Fruit {
name: string
color?: number
[propName: string]: any
}

В данном примере интерфейс Fruit может иметь любое количество свойств. Если это не name или color, то тип свойства не имеет значения.

Третий способ

three

Присвоить объект другой переменной. Из-за присваивания объекта другой переменной он не будет проходить проверку на избыточные свойства, компилятор не выдаст ошибки.

Пример

let options = { name: 'banana', colour: 0xffe135 },
banana = addFruit(options)

Стоит иметь ввиду, что для простого кода не стоит обходить данные проверки свойств. Для более сложных литералов объектов, которые содержат в себе методы, параметры состояния и т.д., стоит держать в памяти данные способы обхода проверок, но все же большинство ошибок, связанных с проверкой лишних свойств, как правило, на самом деле являются ошибками. Если у вас возникает такая ошибка, возможно стоит пересмотреть объявление типа.

Типы функций

function

Помимо описания свойств, интерфейсы также позволяют описывать типы функций.

Для описания типа функции в интерфейсе, в нем нужно определить сигнатуру вызова. Это похоже на объявление функции только со списком параметров и типом возвращаемого значения. Каждый параметр в списке должен иметь имя и тип.

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

Определив такой интерфейс один раз, мы можем его использовать также как и все другие интерфейсы. Пример ниже показывает, как определить переменную с типом функции и присвоить ей значение.

Пример

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

let mySearch: SearchFunc

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

console.log(mySearch('banana lime apple', 'banana'))

Имена параметров не обязательно должны совпадать, чтобы функция прошла проверку на соответствие типов. Мы, к примеру, могли бы записать предыдущий пример — вот так:

Пример

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

let mySearch: SearchFunc

mySearch = (src: string, sub: string): boolean => {
let result = src.search(sub)
if (result == -1) {
return false
} else {
return true
}
}

console.log(mySearch('banana lime apple', 'banana'))

Параметры функций проверяются друг за другом, и типы параметров, находящихся на соответствующих позициях, сравниваются попарно. Если вы не хотите указывать типы для аргументов, то TypeScript сможет вывести типы из контекста, основываясь на том, что функция присваивается переменной, тип которой — SearchFunc. В следующем примере тип возвращаемого значения функции тоже выводится: это делается на основании значений, которые она возвращает false и true. Если бы функция возвращала числа или строки, то компилятор во время проверки типов предупредил бы, что тип возвращаемого значения не совпадает с типом, указанным в интерфейсе SearchFunc.

Пример

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

let mySearch: SearchFunc

mySearch = (src, sub) => {
let result = src.search(sub)
if (result == -1) {
return false
} else {
return true
}
}

console.log(mySearch('banana lime apple', 'banana'))

Индексируемые типы

Аналогично тому, как мы можем использовать интерфейсы для описания типов функций, мы также можем описывать типы, в которые мы можем «индексировать», например, a[10] или ageMap["daniel"]. Индексируемые типы имеют сигнатуру индекса, которая описывает типы, которые мы можем использовать для индексации объекта, вместе с соответствующими типами возврата при индексации.

Пример

interface StringArray {
[index: number]: string
}

let myArray: StringArray
myArray = ['Bob', 'Fred']

let myStr: string = myArray[0]

console.log(myArray[0])

Здесь у нас есть интерфейс StringArray, у которого есть сигнатура индекса. Эта сигнатура говорит о том, что, когда StringArray индексируется числом, возвращается строка.

Расширение интерфейсов

extends

Интерфейсы могут расширять друг друга. Это позволяет вам копировать элементы одного интерфейса в другой, что дает вам больше гибкости в том, как вы разделяете свои интерфейсы на повторно используемые компоненты.

Пример

interface Shape {
color: string
}

interface PenStroke {
penWidth: number
}

// множественное расширение
interface Square extends Shape, PenStroke {
sideLength: number
}

let square = {} as Square
square.color = 'blue'
square.sideLength = 10
square.penWidth = 5.0

Гибридные типы

gibrid

Как мы упоминали ранее, интерфейсы могут описывать более сложные типы, присутствующие в реальном мире JavaScript. Из-за динамического и гибкого характера JavaScript вы можете случайно встретить объект, который работает как комбинация некоторых типов, описанных выше.

Одним из таких примеров является объект, который действует как функция и объект с дополнительными свойствами:

Пример

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

function getCounter(): Counter {
let counter = function (start: number) {} as Counter
counter.interval = 123
counter.reset = function () {}
return counter
}

let c = getCounter()
c(10)
c.reset()
c.interval = 5.0

Вопросы

question

Как называется способ типизации, используемый в TypeScript?

  1. явный
  2. утиный
  3. строгий

С помощью какого ключевого слова объявляется интерфейс?

  1. interface
  2. class
  3. function

С помощью какого символа объявляется необязательное свойство?

  1. !
  2. ?
  3. -

Для чего используется readonly?

  1. Только для чтения
  2. Только для записи
  3. Незнаю

Позволяют ли интерфейсы описывать типы функций?

  1. true
  2. false

С помощью какого ключевого слова расширяются интерфейсы?

  1. yield
  2. extends
  3. export

Теперь мы готовы с вами изучать TypeScript, но для того чтобы понять на сколько вы усвоили этот урок пройдите тест в мобильном приложении в нашей школы по этой теме.

EnglishMoji!

Ссылки

  1. TypeScriptLang
  2. Интерфейсы

Contributors ✨

Thanks goes to these wonderful people (emoji key):


IIo3iTiv


Dmitriy Vasilev

💵

EnglishMoji!