
TypeScript позволяет создавать новые типы на основе существующих, используя интерфейсы, алиасы типов и утилиты для модификации типов. Это обеспечивает точную типизацию данных и упрощает масштабирование крупных проектов.
Интерфейсы позволяют объединять свойства нескольких типов через extends. Например, можно расширить базовый интерфейс User для создания более специализированного типа AdminUser, добавив уникальные поля без дублирования кода.
Алиасы типов совместимы с объединениями и пересечениями. Через type можно создавать комбинированные типы, которые объединяют свойства нескольких интерфейсов или конкретные значения, что особенно полезно при работе с API и строгой валидацией данных.
TypeScript также предоставляет встроенные утилиты для изменения типов: Partial, Required, Pick, Omit. Они позволяют добавлять, удалять или модифицировать свойства существующих типов без переписывания исходного определения, что ускоряет рефакторинг и снижает вероятность ошибок.
Примеры расширения типов на практике помогают выявить несоответствия и ускоряют процесс отладки. Комбинируя интерфейсы, алиасы и утилиты, можно создавать гибкие типовые конструкции, которые отражают сложные бизнес-правила и поддерживают строгую типизацию на всех уровнях приложения.
Создание пользовательских типов с помощью type и interface
type и interface позволяют задать структуру данных и обеспечить статическую проверку типов.
Type
- Определение объекта:
type User = {
id: number;
name: string;
isActive: boolean;
};
- Объединение типов:
type ID = string | number;
- Пересечение типов:
type Employee = User & {
department: string;
};
Interface
- Базовый интерфейс:
interface User {
id: number;
name: string;
isActive: boolean;
}
- Расширение интерфейса:
interface Employee extends User {
department: string;
}
- Реализация интерфейса в классе:
class Manager implements Employee {
id: number;
name: string;
isActive: boolean;
department: string;
constructor(id: number, name: string, department: string) {
this.id = id;
this.name = name;
this.isActive = true;
this.department = department;
}
}
Рекомендации:
- Использовать
typeдля объединений, пересечений и алиасов примитивов. - Использовать
interfaceдля объектов, планируемого расширения и реализации в классах. - Интерфейсы и типы можно комбинировать через
extendsи пересечения (&).
Объединение типов через union для динамических значений

В TypeScript union-типы позволяют объединять несколько возможных типов значения в одну переменную. Это особенно полезно, когда данные могут приходить в разных форматах или изменяться во время выполнения программы.
Пример использования union-типа для динамических значений:
let input: string | number;
Переменная input может содержать либо строку, либо число. TypeScript будет проверять операции над этой переменной, учитывая все варианты типов.
Для корректной работы с union-типами рекомендуется использовать проверку типов через typeof или пользовательские функции проверки:
if (typeof input === "string") {
console.log(input.toUpperCase());
} else {
console.log(input + 10);
}
Union-типы подходят для аргументов функций, которые могут принимать несколько форматов данных. Например:
function formatValue(value: string | number) {
if (typeof value === "string") return value.trim();
return value.toFixed(2);
}
TypeScript также поддерживает объединение с литеральными типами для ограничения значений:
type Status = "success" | "error" | "pending";
Такой подход позволяет использовать union-тип не только для стандартных примитивов, но и для динамических вариантов, обеспечивая строгую типизацию без потери гибкости при обработке разных форматов данных.
Пересечение типов через intersection для комбинирования объектов

В TypeScript пересечение типов (intersection) позволяет объединять свойства нескольких типов в один. Используется оператор &, который создаёт новый тип, включающий все поля исходных типов. Это особенно удобно для объектов с различными наборами свойств.
Пример базового использования:
type Person = {
name: string;
age: number;
};
type Employee = {
employeeId: string;
department: string;
};
type Staff = Person & Employee;
const worker: Staff = {
name: "Иван",
age: 30,
employeeId: "E123",
department: "IT"
};
В примере тип Staff содержит все свойства типов Person и Employee. Если два типа имеют пересекающиеся свойства с разными типами, TypeScript выдаст ошибку.
Таблица демонстрирует правила комбинирования свойств:
| Свойства типов | Результат в пересечении |
|---|---|
| Разные имена | Все свойства сохраняются |
| Одинаковые имена и одинаковый тип | Свойство остаётся одного типа |
| Одинаковые имена и разные типы | Произойдёт ошибка компиляции |
Intersection полезен для комбинирования интерфейсов с функциональными свойствами:
interface Logger {
log: (message: string) => void;
}
interface Notifier {
notify: (msg: string) => void;
}
type System = Logger & Notifier;
const system: System = {
log: (message) => console.log(message),
notify: (msg) => alert(msg)
};
Рекомендации:
- Используйте intersection для объединения объектов, когда требуется полный набор свойств.
- Следите за совпадающими именами свойств, чтобы избежать конфликтов типов.
- При работе с функциями intersection помогает гарантировать наличие всех необходимых методов.
Расширение интерфейсов и наследование типов

В TypeScript интерфейсы могут расширять другие интерфейсы с помощью ключевого слова extends. Это позволяет создавать иерархию типов и повторно использовать общие свойства.
Пример расширения интерфейса:
interface Person { name: string; age: number; }
interface Employee extends Person { employeeId: string; }
В этом примере Employee автоматически получает свойства name и age из Person, добавляя уникальное свойство employeeId. Экземпляры типа Employee могут использовать все три свойства.
Интерфейсы могут расширять несколько интерфейсов одновременно, создавая комбинированные типы:
interface Contact { email: string; }
interface Manager extends Employee, Contact { department: string; }
При наследовании нескольких интерфейсов TypeScript объединяет все их свойства. При совпадении имен свойств важно соблюдать совместимость типов, иначе будет ошибка компиляции.
Кроме интерфейсов, TypeScript поддерживает наследование типов через type с помощью пересечения (&):
type Vehicle = { model: string; }
type Car = Vehicle & { doors: number; }
Пересечение типов позволяет комбинировать объекты без создания интерфейса, но не поддерживает последующее расширение через extends. Интерфейсы предпочтительнее, если планируется дальнейшее наследование.
Рекомендации при расширении интерфейсов: используйте интерфейсы для объектов с иерархией, избегайте дублирования свойств, объединяйте типы через пересечение только для одноразовых комбинаций.
Использование generics для параметризации типов
Generics позволяют создавать функции, интерфейсы и классы с типами, которые задаются при использовании, а не фиксируются заранее. Это повышает гибкость кода и снижает дублирование типов.
Пример функции с generics:
function identity<T>(value: T): T {
return value;
}
Generics также применимы к массивам и объектам. Пример интерфейса для контейнера с элементами одного типа:
interface Container<T> {
items: T[];
add(item: T): void;
}
Создание контейнера для чисел: const numberContainer: Container<number> = { items: [], add(item) { this.items.push(item); } };
Для нескольких параметров типов можно использовать несколько generic-переменных:
function mapPair<K, V>(key: K, value: V): [K, V] {
return [key, value];
}
Функция возвращает кортеж, где тип ключа и значения сохраняется, что исключает ошибки при работе с разными типами.
Generics ограничений позволяют задавать условия для типа. Например, функция, работающая только с объектами, имеющими поле length:
function logLength<T extends { length: number }>(item: T): void {
console.log(item.length);
}
При вызове с объектами без свойства length TypeScript выдаст ошибку компиляции, обеспечивая безопасность типов.
Использование generics в классах позволяет создавать универсальные структуры данных. Пример класса стек:
class Stack<T>
private items: T[] = [];
push(item: T) { this.items.push(item); }
pop(): T
}
Такой стек может хранить любые типы, сохраняя строгую типизацию при работе с элементами.
Создание условных типов с помощью условной логики

В TypeScript условные типы позволяют определять новые типы на основе проверки существующих. Они имеют форму Тип1 extends Тип2 ? ТипA : ТипB, где выбирается один из двух вариантов в зависимости от соответствия условия.
Примеры применения условных типов:
- Простое разделение по типу:
type IsString= T extends string ? "строка" : "не строка"; type A = IsString<string>; // "строка" type B = IsString<number>; // "не строка" - Извлечение типа из массива:
type ElementType= T extends (infer U)[] ? U : T; type Item = ElementType<number[]>; // number type Single = ElementType<boolean>; // boolean - Фильтрация типов в объединениях:
type ExcludeString= T extends string ? never : T; type Result = ExcludeString<string | number | boolean>; // number | boolean
Рекомендации при использовании условных типов:
- Используйте условные типы для вычисления типов на этапе компиляции, чтобы избежать дублирования кода.
- Комбинируйте
inferс условными типами для извлечения внутренних типов массивов, функций и промисов. - Следите за сложностью: глубокие вложенные условные типы могут замедлять компиляцию.
- Используйте их вместе с типами-утилитами (
Partial,Readonly,Pick,Exclude) для динамической трансформации интерфейсов.
Условные типы особенно полезны при построении обобщённых функций, где поведение типов зависит от переданных аргументов, обеспечивая строгую типизацию без явного дублирования.
Манипуляция типами через mapped types и keyof

Mapped types в TypeScript позволяют создавать новые типы на основе существующих, перебирая ключи объекта. Синтаксис выглядит как { [K in keyof T]: ... }, где T – исходный тип, а K – его ключи.
Пример создания типа с обязательными свойствами на основе интерфейса с опциональными:
interface User { name?: string; age?: number; }
type RequiredUser = { [K in keyof User]-?: User[K] };
Здесь -? убирает модификатор optional, делая все поля обязательными.
Mapped types часто комбинируют с keyof для ограничения ключей. Например, выборка только определённых свойств:
type PickUserName = { [K in 'name']: User[K] };
Эта конструкция создаёт тип с одним свойством name>, сохраняя его исходный тип.
Для изменения всех свойств, например, приведения к типу string:
type StringifiedUser = { [K in keyof User]: string };
Таким образом, любые новые ключи, добавленные в User, автоматически будут включены в StringifiedUser как string.
Mapped types поддерживают modifiers readonly и ?. Например, создание неизменяемой версии типа:
type ReadonlyUser = { readonly [K in keyof User]: User[K] };
Использование keyof совместно с условными типами позволяет строить фильтры по ключам:
type NumberKeys
Результат NumberKeys будет 'age', так как только age имеет тип number.
Mapped types в сочетании с keyof дают гибкость для динамического создания, ограничения и модификации типов без дублирования кода, обеспечивая строгую типизацию на всех этапах.
