Перегрузка конструктора в TypeScript: как правильно объявить несколько вариантов?
Правильная реализация перегрузки конструктора в TypeScript
Ваша проблема в том, что сигнатуры перегрузки должны быть объявлены перед реализацией, а сама реализация должна принимать объединение всех возможных типов. Вот как это исправить:
class User {
name: string;
age: number;
// Сигнатуры перегрузки (только объявления)
constructor(name: string, age: number);
constructor(name: string);
constructor(obj: { name: string; age: number });
// Реализация с объединенным типом параметров
constructor(param1: string | { name: string; age: number }, param2?: number) {
if (typeof param1 === 'string') {
this.name = param1;
this.age = param2 ?? 0; // если age не передан, ставим 0
} else {
this.name = param1.name;
this.age = param1.age;
}
}
}
// Примеры использования:
const user1 = new User('Alice', 30); // name: 'Alice', age: 30
const user2 = new User('Bob'); // name: 'Bob', age: 0
const user3 = new User({ name: 'Charlie', age: 25 }); // name: 'Charlie', age: 25
Ключевые моменты:
- Сигнатуры перегрузки — это только объявления (без тела). Они определяют возможные варианты вызова.
- Реализация должна быть одна и принимать наиболее общий тип (объединение всех вариантов).
- Параметры, которые есть не во всех сигнатурах, делаются опциональными (
?) или со значением по умолчанию. - Внутри реализации нужно проверять типы параметров и выполнять соответствующую логику.
Почему это работает?
TypeScript проверяет сигнатуры перегрузки при вызове конструктора, но во время выполнения используется только одна реализация. Поэтому внутри реализации мы должны сами обработать все варианты. Использование typeof и проверки на undefined — стандартный подход.
Более сложный пример с защитниками типов:
class Rectangle {
width: number;
height: number;
constructor(width: number, height: number);
constructor(size: number); // квадрат
constructor(dimensions: { width: number; height: number });
constructor(first: number | { width: number; height: number }, second?: number) {
if (typeof first === 'number' && second !== undefined) {
// Вариант: (width, height)
this.width = first;
this.height = second;
} else if (typeof first === 'number') {
// Вариант: (size) — квадрат
this.width = first;
this.height = first;
} else {
// Вариант: ({ width, height })
this.width = first.width;
this.height = first.height;
}
}
}
Альтернативный подход с фабричными методами
Если перегрузка становится слишком сложной, можно использовать статические фабричные методы:
class User {
private constructor(public name: string, public age: number) {}
static withName(name: string): User {
return new User(name, 0);
}
static withNameAndAge(name: string, age: number): User {
return new User(name, age);
}
static fromObject(obj: { name: string; age: number }): User {
return new User(obj.name, obj.age);
}
}
const user = User.withName('Alice');
Этот подход делает код более читаемым и избавляет от сложных проверок типов.
Итог
Перегрузка конструктора в TypeScript работает, но требует явного объявления всех сигнатур и одной реализации, которая обрабатывает все случаи. Главное — помнить о порядке: сначала сигнатуры, потом реализация. Если вариантов много, рассмотрите фабричные методы или паттерн "Строитель".
<<Использование опциональных параметров и значений по умолчанию
Вместо перегрузки конструктора можно часто обойтись одним конструктором с опциональными параметрами и значениями по умолчанию. Это проще и часто более читаемо:
class User {
name: string;
age: number;
constructor(name: string, age: number = 0) {
this.name = name;
this.age = age;
}
}
// Использование:
const user1 = new User('Alice', 30); // age = 30
const user2 = new User('Bob'); // age = 0
Этот подход покрывает первые два варианта из вашего вопроса. Для третьего варианта (прием объекта) можно добавить перегрузку, но если нужно именно три варианта, лучше использовать объединение типов:
class User {
name: string;
age: number;
constructor(param: string | { name: string; age: number }, age?: number) {
if (typeof param === 'string') {
this.name = param;
this.age = age ?? 0;
} else {
this.name = param.name;
this.age = param.age;
}
}
}
Такой подход избавляет от необходимости писать несколько сигнатур перегрузки, а вся логика остается в одном конструкторе. TypeScript сам выведет типы при вызове.