Avatar ·

Значения по умолчанию для интерфейсов TypeScript: как задать?

Всем привет! Я недавно начал изучать TypeScript и столкнулся с проблемой. У меня есть интерфейс для конфигурации приложения: ```typescript interface UserConfig { name: string; age: number; isActive: boolean; email?: string; } ``` Когда я создаю объект, мне приходится явно указывать все поля, даже те, которые я хочу сделать необязательными. Например: ```typescript const config: UserConfig = { name: "Иван", age: 25, isActive: true // email не указан }; ``` Но мне нужно, чтобы по умолчанию у полей `isActive` было значение `true`, а у `email` — `""` (пустая строка), если они не указаны. Как это сделать? Можно ли задать значения по умолчанию прямо в интерфейсе? Или нужно использовать классы? Я пробовал писать так: ```typescript interface UserConfig { name: string; age: number; isActive: boolean = true; // Ошибка! email?: string = ""; // Тоже ошибка } ``` TypeScript ругается на это. Подскажите, как правильно реализовать значения по умолчанию для полей интерфейса? Заранее спасибо! <<>>

Проблема: интерфейсы TypeScript не поддерживают значения по умолчанию

Вы правы — в TypeScript интерфейсы описывают только структуру данных (типы полей), но не могут содержать значения по умолчанию. Это сделано намеренно, чтобы отделить описание типа от реализации. Однако есть несколько проверенных способов решить вашу задачу.

Способ 1: Функция-фабрика с оператором spread (рекомендуется)

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

interface UserConfig {
  name: string;
  age: number;
  isActive: boolean;
  email: string;
}

function createUserConfig(overrides?: Partial<UserConfig>): UserConfig {
  const defaults: UserConfig = {
    name: "Гость",
    age: 18,
    isActive: true,
    email: ""
  };
  return { ...defaults, ...overrides };
}

// Использование:
const config1 = createUserConfig({ name: "Иван", age: 25 });
console.log(config1);
// { name: "Иван", age: 25, isActive: true, email: "" }

const config2 = createUserConfig();
console.log(config2);
// { name: "Гость", age: 18, isActive: true, email: "" }

Как это работает:

  • Тип Partial<UserConfig> делает все поля необязательными
  • Оператор ... (spread) объединяет объекты, причем значения из overrides перезаписывают defaults
  • Функция гарантированно возвращает полный объект UserConfig

Способ 2: Использование класса с конструктором

Если вам нужна не только структура, но и поведение (методы), используйте класс:

class UserConfigClass {
  name: string;
  age: number;
  isActive: boolean;
  email: string;

  constructor(data?: Partial<UserConfigClass>) {
    this.name = data?.name ?? "Гость";
    this.age = data?.age ?? 18;
    this.isActive = data?.isActive ?? true;
    this.email = data?.email ?? "";
  }
}

const config = new UserConfigClass({ name: "Мария" });
console.log(config);
// UserConfigClass { name: "Мария", age: 18, isActive: true, email: "" }

В этом случае сам класс служит и интерфейсом, и реализацией. TypeScript автоматически выводит тип из класса.

Способ 3: Утилитарный тип с объединением

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

interface UserConfigRequired {
  name: string;
  age: number;
}

interface UserConfigDefaults {
  isActive: boolean;
  email: string;
}

type UserConfig = UserConfigRequired & Partial<UserConfigDefaults>;

// Но значения по умолчанию всё равно нужно задавать вручную:
function applyDefaults(config: UserConfig): UserConfigRequired & UserConfigDefaults {
  return {
    ...config,
    isActive: config.isActive ?? true,
    email: config.email ?? ""
  };
}

Важные замечания

  • Не пытайтесь использовать ?? (nullish coalescing) прямо в интерфейсе — это синтаксически неверно
  • Для простых случаев используйте фабричную функцию — это наиболее идиоматичный подход в TypeScript
  • Если полей много (10+), рассмотрите библиотеки вроде ts-pattern или zod для валидации с дефолтами

Выберите способ, который лучше всего подходит под вашу архитектуру. Для большинства проектов достаточно первого варианта с Partial и spread-оператором.

<<>>

Альтернативный подход: деструктуризация с значениями по умолчанию

Можно задать значения по умолчанию прямо при деструктуризации объекта. Это удобно, если вы работаете с данными локально, а не создаёте новые объекты:

interface UserConfig {
  name: string;
  age: number;
  isActive?: boolean;
  email?: string;
}

function processUser(data: UserConfig) {
  const { 
    name, 
    age, 
    isActive = true, 
    email = "" 
  } = data;
  
  // Теперь isActive и email гарантированно имеют значения
  console.log(`Пользователь ${name}, возраст ${age}, активен: ${isActive}, email: ${email}`);
}

// Использование:
processUser({ name: "Анна", age: 30 });
// Выведет: Пользователь Анна, возраст 30, активен: true, email: 

Плюсы: не нужно писать отдельную функцию, код компактный.

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

Также можно комбинировать с фабричной функцией для создания объектов:

function createConfig({ 
  name, 
  age, 
  isActive = true, 
  email = "" 
}: Partial<UserConfig>): UserConfig {
  return { name: name ?? "Гость", age: age ?? 18, isActive, email };
}

Этот вариант особенно полезен, когда у вас много необязательных полей с разными дефолтами.