В этом руководстве вы узнаете об интерфейсах TypeScript и о том, как их использовать для принудительной проверки типов.
Введение в интерфейсы TypeScript
Интерфейсы TypeScript определяют контракты в вашем коде. Они также предоставляют явные имена для проверки типов.
Начнем с простого примера:
function getFullName(person: { firstName: string; lastName: string }) { return `${person.firstName} ${person.lastName}`; } let person = { firstName: 'John', lastName: 'Doe' }; console.log(getFullName(person));
Выход:
John Doe
В этом примере компилятор TypeScript проверяет аргумент, который вы передаете в функцию getFullName()
.
Если аргумент имеет два свойства, типы которых являются строковыми, то компилятор TypeScript проходит проверку. В противном случае выдаст ошибку .
Как видно из кода, аннотация типа аргумента функции затрудняет чтение кода.
Чтобы решить эту проблему, TypeScript вводит концепцию интерфейсов.
В следующем примере используется интерфейс Person
с двумя строковыми свойствами:
interface Person { firstName: string; lastName: string; }
По соглашению имена интерфейсов пишутся в верхнем регистре. Они используют одну заглавную букву для разделения слов в именах. Например, Person
, UserProfile
и FullName
.
После определения интерфейса Person
вы можете использовать его как тип. И вы можете аннотировать параметр функции именем интерфейса:
function getFullName(person: Person) { return `${person.firstName} ${person.lastName}`; } let john = { firstName: 'John', lastName: 'Doe' }; console.log(getFullName(john));
Код теперь легче читать, чем раньше.
Функция getFullName()
будет принимать любой аргумент, который имеет два строковых свойства. И не обязательно иметь ровно два строковых свойства. См. следующий пример:
Следующий код объявляет объект с четырьмя свойствами:
let jane = { firstName: 'Jane', middleName: 'K.' lastName: 'Doe', age: 22 };
Поскольку объект jane
имеет два строковых свойства firstName
и lastName
, вы можете передать его в функцию getFullName()
следующим образом:
let fullName = getFullName(jane); console.log(fullName); // Jane Doe
Дополнительные свойства
Интерфейс может иметь необязательные свойства. Чтобы объявить необязательное свойство, вы используете вопросительный знак( ?)
в конце имени свойства в объявлении, например:
interface Person { firstName: string; middleName?: string; lastName: string; }
В этом примере интерфейс Person
имеет два обязательных и одно необязательное свойство.
А ниже показано, как использовать интерфейс Person
в функции getFullName()
:
function getFullName(person: Person) { if(person.middleName) { return `${person.firstName} ${person.middleName} ${person.lastName}`; } return `${person.firstName} ${person.lastName}`; }
Свойства только для чтения
Если свойства должны изменяться только при первом создании объекта, вы можете использовать ключевое слово readonly
перед именем свойства:
interface Person { readonly ssn: string; firstName: string; lastName: string; } let person: Person; person = { ssn: '171-28-0926', firstName: 'John', lastName: 'Doe' }
В этом примере свойство ssn
изменить нельзя:
person.ssn = '171-28-0000';
Ошибка:
error TS2540: Cannot assign to 'ssn' because it is a read-only property.
Типы функций
В дополнение к описанию объекта со свойствами, интерфейсы также позволяют вам описывать типы функций.
Чтобы описать тип функции, вы назначаете интерфейс сигнатуре функции, которая содержит список параметров с типами и возвращаемыми типами. Например:
interface StringFormat { (str: string, isUpper: boolean): string }
Теперь вы можете использовать этот интерфейс функционального типа.
Ниже показано, как объявить переменную типа функции и присвоить ей значение функции того же типа:
let format: StringFormat; format = function(str: string, isUpper: boolean) { return isUpper ? str.toLocaleUpperCase() : str.toLocaleLowerCase(); }; console.log(format('hi', true));
Выход:
HI
Обратите внимание, что имена параметров не обязательно должны совпадать с сигнатурой функции. Следующий пример эквивалентен приведенному выше примеру:
let format: StringFormat; format = function(src: string, upper: boolean) { return upper ? src.toLocaleUpperCase() : src.toLocaleLowerCase(); }; console.log(format('hi', true));
Интерфейс StringFormat
гарантирует, что все вызывающие функции, которые его реализуют, передают необходимые аргументы: string
и boolean
.
Следующий код также прекрасно работает, хотя lowerCase
назначен функции, не имеющей второго аргумента:
let lowerCase: StringFormat; lowerCase = function(str: string) { return str.toLowerCase(); } console.log(lowerCase('Hi', false));
Обратите внимание, что второй аргумент передается при вызове функции lowerCase()
.
Типы классов
Если вы работали с Java или C#, вы можете обнаружить, что в основном интерфейс используется для определения контракта между несвязанными классами.
Например, следующий интерфейс Json
может быть реализован любыми несвязанными классами:
interface Json { toJSON(): string }
Далее объявляется класс, реализующий интерфейс Json
:
class Person implements Json { constructor(private firstName: string, private lastName: string) { } toJson(): string { return JSON.stringify(this); } }
В классе Person
мы реализовали метод toJson()
Json
интерфейса SnNvbg==.
В следующем примере показано, как использовать класс Person
:
let person = new Person('John', 'Doe'); console.log(person.toJson());
Выход:
{"firstName":"John","lastName":"Doe"}
Заключение
- Интерфейсы TypeScript определяют контракты в коде и предоставляют явные имена для проверки типов.
- Могут иметь необязательные свойства или свойства только для чтения.
- Интерфейсы могут использоваться как типы функций.
- Обычно используются как типы классов, которые заключают контракт между несвязанными классами в TypeScript.