Как задать значение по умолчанию для объекта-параметра функции в TypeScript?
Основная проблема и её решение
Вы столкнулись с классической ситуацией, когда TypeScript не позволяет присвоить литерал объекта типу, если в этом литерале не указаны все обязательные поля. Дело в том, что TypeScript проверяет избыточность свойств (excess property checks) при прямом присваивании литерала. Однако ваш код корректен по логике — вы просто хотите задать значения по умолчанию.
Самый простой и правильный способ — использовать пересечение типов (Partial) для параметра и явно указать значения по умолчанию в теле функции. Вот как это делается:
interface UserConfig {
name: string;
age: number;
isAdmin: boolean;
}
// Дефолтные значения
const defaultConfig: UserConfig = {
name: 'Guest',
age: 18,
isAdmin: false,
};
function createUser(config: Partial<UserConfig> = {}) {
// Объединяем переданные значения с дефолтными
const finalConfig: UserConfig = { ...defaultConfig, ...config };
console.log(finalConfig.name, finalConfig.age, finalConfig.isAdmin);
}
// Использование:
createUser(); // Guest, 18, false
createUser({ name: 'Alice' }); // Alice, 18, false
createUser({ name: 'Bob', age: 25 }); // Bob, 25, false
createUser({ isAdmin: true }); // Guest, 18, true
Почему это работает?
Partial<UserConfig>делает все поля необязательными:{ name?: string; age?: number; isAdmin?: boolean }.- Значение по умолчанию
{}— пустой объект, который совместим сPartial. - Оператор
...(spread) объединяет переданные свойства с дефолтными, причём переданные значения перезаписывают дефолтные. - Итоговый объект
finalConfigгарантированно имеет типUserConfig— все поля обязательны и имеют корректные значения.
Более строгий вариант с дженериками
Если вы хотите, чтобы TypeScript проверял, что передаются только существующие свойства (без опечаток), можно добавить дженерик:
function createUser<T extends Partial<UserConfig>>(config: T = {} as T) {
const finalConfig: UserConfig = { ...defaultConfig, ...config };
// ...
}
// Ошибка! Свойства 'namee' не существует в UserConfig
createUser({ namee: 'Test' });
Альтернатива — использовать Required
Можно также сделать все поля в интерфейсе необязательными, а внутри функции использовать Required для итогового типа:
interface UserConfig {
name?: string;
age?: number;
isAdmin?: boolean;
}
const defaultConfig: Required<UserConfig> = {
name: 'Guest',
age: 18,
isAdmin: false,
};
function createUser(config: UserConfig = {}) {
const finalConfig: Required<UserConfig> = { ...defaultConfig, ...config };
// ...
}
Но этот вариант менее удобен, так как интерфейс теряет информацию об обязательности полей при вызове функции.
Рекомендация
Используйте первый вариант с Partial и спред-оператором. Он:
- Чётко разделяет обязательные и необязательные поля в интерфейсе.
- Не требует дублирования дефолтных значений в сигнатуре функции.
- Хорошо масштабируется — можно легко добавить новые поля.
- Полностью типизирован на всех этапах.
Единственный минус — вы теряете проверку на избыточные свойства при передаче объекта. Но это решается дженериком, как показано выше.
<<Использование фабричной функции или класса
Если вам часто нужно создавать объекты с дефолтными значениями, можно вынести логику в отдельную сущность. Это особенно удобно, когда дефолты сложные или их много.
interface UserConfig {
name: string;
age: number;
isAdmin: boolean;
}
class UserConfigBuilder {
private config: UserConfig = {
name: 'Guest',
age: 18,
isAdmin: false,
};
withName(name: string): this {
this.config.name = name;
return this;
}
withAge(age: number): this {
this.config.age = age;
return this;
}
withAdmin(isAdmin: boolean): this {
this.config.isAdmin = isAdmin;
return this;
}
build(): UserConfig {
return { ...this.config };
}
}
function createUser(config?: Partial<UserConfig>) {
const finalConfig: UserConfig = config
? { ...new UserConfigBuilder().build(), ...config }
: new UserConfigBuilder().build();
// ...
}
Этот подход даёт читаемый fluent-интерфейс и строгую типизацию. Однако он избыточен для простых случаев.
Использование утилитарного типа Defaults
Можно создать вспомогательный тип, который автоматически подставляет дефолты:
type WithDefaults<T, D extends Partial<T>> = Omit<T, keyof D> & Partial<D>;
interface UserConfig {
name: string;
age: number;
isAdmin: boolean;
}
type UserConfigInput = WithDefaults<UserConfig, { name: string; age: number }>;
// Результат: { name: string; age: number; isAdmin?: boolean }
function createUser(config: UserConfigInput = { name: 'Guest', age: 18 }) {
const finalConfig: UserConfig = {
isAdmin: false, // дефолт для isAdmin
...config
};
// ...
}
Этот вариант даёт тонкий контроль над тем, какие поля обязательны при вызове, а какие имеют дефолты. Но требует дополнительного типа и может запутать новичков.
Вывод: Для 90% случаев достаточно первого ответа с Partial и спредом. Остальные варианты — для специфических требований проекта.
Ответов пока нет.
Войдите чтобы оставить ответ
Блоги