В этом уроке мы разберем использование интерфейсов. В предыдущем уроке мы рассказывали, что их работа похожа на работу типов в TypeScript. Но у них есть и свои особенности, которые мы затронем сегодня.
Если интерфейс необходимо расширить дополнительными полями после его инициализации, мы можем повторно объявить интерфейс с новыми свойствами. Такой способ называется слиянием деклараций:
interface IUser {
rating: number;
}
interface IUser {
nickname: string;
birthdate: number;
}
const sergey: IUser = {
nickname: 'Sergey',
birthdate: 1990,
rating: 1102,
}
Здесь мы создали интерфейс IUser
, а затем для демонстрационных целей расширили его новыми свойствами. После этого создали на его основе объект Sergey
.
Рассмотрим другой способ работы с интерфейсом.
Мы можем расширить интерфейс с помощью создания другого интерфейса, который наследуется от него:
interface IStudent extends IUser {
group: number;
}
const sergey: IStudent = {
nickname: 'Sergey',
birthdate: 1990,
rating: 1102,
group: 2,
}
В этом примере мы создали на основе нашего предыдущего интерфейса IUser
еще один — IStudent
, в который добавили свойство group
. Так интерфейс IStudent
имеет все свойства IUser
и все свойства, которые мы указали при его расширении от IUser
, то есть дополнительно group
.
Теперь рассмотрим, как работать с несколькими интерфейсами.
Еще интерфейсы могут расширять сразу несколько других интерфейсов:
interface IUser {
nickname: string;
rating: number;
}
interface IEditor {
courses: [string];
canEdit: boolean;
}
interface IAuthor extends IUser, IEditor {
team: string;
}
const sergey: IAuthor = {
nickname: 'Sergey',
rating: 20,
courses: ['typescript'],
canEdit: true,
team: 'Hexlet College'
}
В примере выше мы создали экземпляр на основе интерфейса IAuthor
, который был создан путем расширения интерфейсов IUser
и IEditor
. Этот экземпляр взял в себя все свойства данных интерфейсов и свойство, которое мы указали при создании самого интерфейса IAuthor
.
Также TypeScript позволяет нам создавать перекрестные типы (intersection types) из нескольких интерфейсов c помощью литерала &
:
interface IOneWay {
one: string;
}
interface IOrAnother {
another: string;
}
type OneWayOrAnother = IOneWay & IOrAnother;
const example: OneWayOrAnother = {
one: 'A',
another: 'B',
}
Здесь мы создали тип OneWayOrAnother
на основе двух интерфейсов при помощи литерала &
. Данный тип включил в себя все свойства указанных интерфейсов.
Между созданием перекрестных типов и расширением интерфейсов нет существенных отличий. Почти всегда эти действия будут взаимозаменяемыми, поэтому это скорее вопрос удобства. Но существуют исключения, где расширение интерфейса ведет себя не так, как создание перекрестного типа.
Может случиться так, что мы не знаем заранее всех свойств, которые будут содержаться в нашем интерфейсе. Но нам известно их возможное содержание. В таком случае удобно использовать специальную индексную сигнатуру, которая позволяет описать типы возможных значений:
interface IPhoneBook {
[index:string]: number;
}
const myNotePad: IPhoneBook = {
ivan: 55531311,
sergey: 55500110,
mom: 55522111,
}
В примере выше мы решили вопрос создания телефонной книги с помощью индексной сигнатуры. Это позволило нам не указывать множество свойств с именами, но лишь один раз указать тип ключа и тип его значения.
Интерфейсы — это еще один мощный инструмент в TypeScript наряду с типами. Он позволяет гибко описать наши данные. Также он удобно поддается расширению и объединению с другими типами или интерфейсами.
Вам даны несколько интерфейсов. На их основе создайте интерфейс ISuperman. ISuperMan должен иметь метод guessWho, принимающий и возвращающий строку.
На основе интерфейса ISuperMan создайте объект superMan
. Метод guessWho должен работать следующим образом: если в качестве строки в аргументе приходит любое значение кроме superman (в любом регистре), то следует вернуть предположение "It's a ${value}?", иначе "It's a ${value}!".
console.log(superMan.guessWho('bird')); // "It's a bird?";
console.log(superMan.guessWho('plane')); "It's a plane?";
console.log(superMan.guessWho('superman')); "It's a superman!";
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.
Ваше упражнение проверяется по этим тестам
1import superMan from './index';
2
3test('guess who', () => {
4 expect(superMan.guessWho('bird')).toBe("It's a bird?");
5 expect(superMan.guessWho('plane')).toBe("It's a plane?");
6 expect(superMan.guessWho('SupErMan')).toBe("It's a SupErMan!");
7});
8
Решение учителя откроется через: