Другое

Как расширить типы в TypeScript: Полное руководство

Узнайте, как правильно расширять типы в TypeScript с помощью операторов пересечения, ключевого слова extends и паттернов наследования. Руководство с примерами.

Как расширить типы в TypeScript?

У меня есть следующий тип TypeScript:

typescript
type Event = {
   name: string;
   dateCreated: string;
   type: string;
}

Я хочу расширить этот тип, чтобы создать новый тип с дополнительными свойствами:

typescript
type UserEvent extends Event = {
   UserId: string; 
}

Однако такой синтаксис не работает. Как правильно расширить тип TypeScript в этом случае?

TypeScript не позволяет тип‑алиасам напрямую использовать ключевое слово extends, как это делают интерфейсы. Чтобы расширить тип Event, необходимо использовать оператор пересечения (&), чтобы объединить базовый тип с новыми свойствами.


Содержание


Базовое расширение типов с помощью операторов пересечения

Для тип‑алиасов правильный подход — использовать оператор пересечения (&), чтобы объединить базовый тип с вашими новыми свойствами. Вот как исправить ваш пример:

typescript
type Event = {
   name: string;
   dateCreated: string;
   type: string;
}

// Правильный способ расширить тип‑алиас
type UserEvent = Event & {
   UserId: string;
}

Это создаёт тип UserEvent, который включает все свойства из Event плюс дополнительное свойство UserId. Как объясняет bobbyhadz.com, «оператор & и ключевое слово extends позволяют копировать члены других именованных типов и добавлять новые члены для создания нового типа».

Теперь вы можете использовать этот тип как ожидается:

typescript
const userEvent: UserEvent = {
   name: "Tech Conference",
   dateCreated: "2024-01-15",
   type: "conference",
   UserId: "user123"
}

Подход с пересечением является стандартным способом достижения расширения типов с тип‑алиасами в TypeScript.


Расширение интерфейсов с ключевым словом extends

Если вы предпочитаете использовать ключевое слово extends, вы можете преобразовать ваш тип в интерфейс:

typescript
interface Event {
   name: string;
   dateCreated: string;
   type: string;
}

// Интерфейс может напрямую использовать extends
interface UserEvent extends Event {
   UserId: string;
}

Согласно документации TypeScript, интерфейсы «могут быть расширены с помощью ключевого слова extends». Это естественный способ создать поведение, похожее на наследование, в TypeScript.

Ключевое преимущество использования интерфейсов заключается в поддержке объединения деклараций — вы можете добавлять новые свойства к существующему интерфейсу в разных декларациях:

typescript
interface Event {
   name: string;
   dateCreated: string;
   type: string;
}

// Позже в вашем коде вы можете расширить тот же интерфейс
interface Event {
   organizer?: string;
}

interface UserEvent extends Event {
   UserId: string;
}

Наследование классов в TypeScript

Для классов вы также можете использовать ключевое слово extends, чтобы создать наследование:

typescript
class Event {
   constructor(
      public name: string,
      public dateCreated: string,
      public type: string
   ) {}
}

class UserEvent extends Event {
   constructor(
      name: string,
      dateCreated: string,
      type: string,
      public UserId: string
   ) {
      super(name, dateCreated, type);
   }
}

Как объясняет TypeScript Handbook, «этот пример демонстрирует базовую функцию наследования: классы наследуют свойства и методы от базовых классов. Здесь Dog является производным классом, который наследует от базового класса Animal с помощью ключевого слова extends».

При расширении классов необходимо вызывать конструктор родительского класса с помощью super() в конструкторе дочернего класса.


Множественное наследование и объединение деклараций

Интерфейсы могут наследовать несколько интерфейсов с помощью ключевого слова extends:

typescript
interface Person {
   name: string;
   age: number;
}

interface Address {
   street: string;
   city: string;
}

interface Employee extends Person, Address {
   jobTitle: string;
}

const employee: Employee = {
   name: "John Doe",
   age: 30,
   street: "123 Main St",
   city: "Anytown",
   jobTitle: "Developer"
};

Однако тип‑алиасы не могут напрямую использовать множественное наследование. Вам понадобится цепочка операторов пересечения:

typescript
type Person = {
   name: string;
   age: number;
}

type Address = {
   street: string;
   city: string;
}

type Employee = Person & Address & {
   jobTitle: string;
}

Продвинутые шаблоны расширения типов

Обобщенные типы

Вы можете создавать обобщенные типы, которые расширяют базовые типы:

typescript
type BaseEvent<T> = {
   id: string;
   data: T;
   timestamp: Date;
}

type UserEvent = BaseEvent<User> & {
   UserId: string;
}

type User = {
   username: string;
   email: string;
}

Условные типы

Ключевое слово extends также используется в условных типах:

typescript
type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>;  // true
type Test2 = IsString<number>;  // false

Частичное расширение

Вы можете создавать частичные расширения с помощью встроенных типов:

typescript
type OptionalUserId = Partial<UserEvent>;

const partialEvent: OptionalUserId = {
   name: "Event",
   type: "meeting"
   // UserId является необязательным
}

Лучшие практики расширения типов

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

  2. Предпочитайте композицию над наследованием: вместо глубоких иерархий наследования рассмотрите композицию с помощью операторов пересечения:

typescript
type EventLogger = Event & {
   logger: Logger;
}

type EventValidator = Event & {
   validator: Validator;
}
  1. Будьте внимательны к объединению деклараций: помните, что интерфейсы могут быть расширены в нескольких местах, что может привести к неожиданному поведению, если не управлять этим аккуратно.

  2. Используйте встроенные типы: TypeScript предоставляет встроенные типы, такие как Partial, Pick, Omit и Record, которые могут помочь при расширении типов в типичных сценариях.

  3. Учитывайте производительность: сложные типы пересечения иногда могут влиять на скорость проверки типов, особенно в больших кодовых базах.


Заключение

Чтобы расширить типы в TypeScript, у вас есть несколько вариантов в зависимости от конкретной задачи:

  • Для тип‑алиасов: используйте оператор пересечения (&) для объединения типов.
  • Для интерфейсов: используйте ключевое слово extends напрямую.
  • Для классов: используйте ключевое слово extends с правильными вызовами super().

Главный вывод — тип‑алиасы не могут напрямую использовать ключевое слово extends, но типы пересечения предоставляют эквивалентную функциональность. При работе с сложными иерархиями типов рассмотрите композицию вместо глубокого наследования и воспользуйтесь встроенными типами TypeScript для распространённых шаблонов расширения.

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


Источники

  1. How to extend a Type in TypeScript | bobbyhadz
  2. TypeScript: Handbook - Classes
  3. Extending types in TypeScript | Graphite
  4. TypeScript Extend Interface | TypeScript Tutorial
  5. TypeScript Type Aliases and Interfaces | W3Schools
  6. Three Ways of Using “extends” in TypeScript | DEV Community
  7. Interfaces vs Type Aliases: what’s the difference? | YouTube
Авторы
Проверено модерацией
Модерация