В этом руководстве вы узнаете об интерфейсах 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.
