Зарегистрируйтесь для доступа к 15+ бесплатным курсам по программированию с тренажером

Структурная типизация Основы Typescript

В JavaScript возможно работать с объектами и классами одинаковым образом. При этом не нужно опираться ни на наследование, ни на интерфейсы. Нужны только ожидаемые поля и методы. Такой подход называют утиной типизацией (duck typing). Если что-то ходит как утка и крякает как утка — значит, это утка:

const user = {
  firstName: 'Vasiliy',
  lastName: 'Kuzenkov',
  type: 'user'
}

const admin = {
  firstName: 'Kirill',
  lastName: 'Mokevnin',
  type: 'admin'
}

const formatUser = (user) => [user.type, ':', user.firstName, user.lastName].join(' ');

formatUser(user); // 'user : Vasiliy Kuzenkov'
formatUser(admin); // 'admin : Kirill Mokevnin'

В языках как Java нам бы потребовалось определить интерфейс, после отдельно имплементировать его для классов User и Admin. А в параметрах метода форматирования тип аргумента был бы этим интерфейсом.

Другой вариант — написать метод с перегрузкой для этих двух случаев. Языки с таким поведением используют номинативную типизацию (nominative typing).

Чтобы организовать подход утиной типизации в Java, нужно написать много дополнительного кода.

Чтобы упростить переход с JavaScript на TypeScript и использовать проверки до выполнения кода, был выбран подход структурной типизации. С ней мы и познакомимся в этом уроке.

С помощью структурной типизации мы можем легко переписать наш пример на TypeScript:

const user = {
  firstName: 'Vasiliy',
  lastName: 'Kuzenkov',
  type: 'user'
}

const admin = {
  firstName: 'Kirill',
  lastName: 'Mokevnin',
  type: 'admin'
}

const formatUser = (user: { type: string, firstName: string, lastName: string }): string =>
  [user.type, ':', user.firstName, user.lastName].join(' ');

formatUser(user); // 'user : Vasiliy Kuzenkov'
formatUser(admin); // 'admin : Kirill Mokevnin'

При этом структурная типизация не защищает нас от наличия дополнительных полей в объекте:

const moderator = {
  firstName: 'Danil',
  lastName: 'Polovinkin',
  type: 'moderator',
  email: 'danil@polovinkin.com'
}

const formatUser = (user: { type: string, firstName: string, lastName: string }): string =>
  [user.type, ':', user.firstName, user.lastName].join(' ');

formatUser(moderator); // 'moderator : Danil Polovinkin'

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

object

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

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

При объединении | мы расширяем тип — увеличиваем число допустимых значений для типа. А при пересечении & — сужаем. Так мы уменьшаем число допустимых значений:

type IntersectionUser = {
  username: string;
  password: string;
} & {
    type: string;
}

const admin: IntersectionUser = { username: 'test', password: 'test', type: 'admin' } // Требуется совпадение c объектным типом и слева и справа от оператора &

type UnionUser = {
    username: string;
    password: string;
} | {
    type: string;
}

const user: UnionUser = { username: 'test', type: 'user' } // Достаточно совпадения с одним из объектных типов

object intersection

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

Ответ При пересечении объектных типов, если встречаются поля с одинаковыми именами, то они должны быть совместимы — иметь одинаковый тип. Иначе будет ошибка компиляции, потому что итоговый тип будет `never`.

Аватары экспертов Хекслета

Остались вопросы? Задайте их в разделе «Обсуждение»

Вам ответят команда поддержки Хекслета или другие студенты

Об обучении на Хекслете

Для полного доступа к курсу нужен базовый план

Базовый план откроет полный доступ ко всем курсам, упражнениям и урокам Хекслета, проектам и пожизненный доступ к теории пройденных уроков. Подписку можно отменить в любой момент.

Получить доступ
1000
упражнений
2000+
часов теории
3200
тестов

Открыть доступ

Курсы программирования для новичков и опытных разработчиков. Начните обучение бесплатно

  • 130 курсов, 2000+ часов теории
  • 1000 практических заданий в браузере
  • 360 000 студентов
Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»

Наши выпускники работают в компаниях:

Логотип компании Альфа Банк
Логотип компании Aviasales
Логотип компании Yandex
Логотип компании Tinkoff
Рекомендуемые программы
профессия
от 6 300 ₽ в месяц
Разработка фронтенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 18 апреля
профессия
от 9 900 ₽ в месяц
Разработка фронтенд- и бэкенд-компонентов для веб-приложений
16 месяцев
с нуля
Старт 18 апреля
профессия
от 6 300 ₽ в месяц
Разработка бэкенд-компонентов для веб-приложений
10 месяцев
с нуля
Старт 18 апреля

Используйте Хекслет по-максимуму!

  • Задавайте вопросы по уроку
  • Проверяйте знания в квизах
  • Проходите практику прямо в браузере
  • Отслеживайте свой прогресс

Зарегистрируйтесь или войдите в свой аккаунт

Отправляя форму, вы принимаете «Соглашение об обработке персональных данных» и условия «Оферты», а также соглашаетесь с «Условиями использования»