Property has no initializer в TypeScript: как исправить за 5 минут

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Вступление: почему TypeScript ругается на ваши свойства?

Вы пишете код на TypeScript, всё идёт отлично... И вдруг — бац! Красная волнистая линия под свойством класса, а компилятор выдаёт леденящую душу фразу: Property '...' has no initializer and is not definitely assigned in the constructor. Знакомо? Если вы хоть раз работали с Angular, React или просто с TypeScript-классами — скорее всего, да.

Эта ошибка — одна из самых популярных на Stack Overflow (тысячи просмотров, десятки ответов). Но хорошая новость: она не страшная. Плохая новость: если не разобраться, можно наломать дров с багами в рантайме. В этой статье мы разберём, почему возникает эта ошибка TypeScript, как её исправить и когда каждое из решений уместно.

Что означает ошибка "Property has no initializer"?

Давайте переведём с компиляторного на человеческий. TypeScript говорит вам: "Эй, ты объявил свойство в классе, но не дал ему начального значения и не присвоил его в конструкторе. Я не уверен, что там будет лежать что-то осмысленное, когда ты обратишься к этому свойству".

Включите strict: true в tsconfig.json — и вы автоматически активируете флаг strictPropertyInitialization. Именно он отвечает за эту проверку. Выглядит код примерно так:

class User {
  name: string; // ❌ Ошибка!
  age: number;  // ❌ Ошибка!
}

TypeScript справедливо замечает: name и age могут оказаться undefined, если вы забудете их инициализировать. А тип у них — строгий string и number, без undefined. Вот вам и конфликт.

Способ №1: Инициализация прямо в объявлении

Самый простой и безопасный путь — дать свойству значение сразу. Это особенно удобно для полей со значениями по умолчанию.

class User {
  name: string = 'Гость';
  age: number = 0;
  createdAt: Date = new Date();
}

Когда использовать: когда у поля есть разумное значение по умолчанию. Например, счётчик, флаг, дата создания. Это лучшая практика TypeScript — вы явно указываете начальное состояние.

Способ №2: Инициализация в конструкторе

Классика жанра. Если значение должно прийти извне — присваивайте в конструкторе. TypeScript это одобряет.

class User {
  name: string;
  age: number;

  constructor(name: string, age: number) {
    this.name = name;
    this.age = age;
  }
}

Важно: компилятор проверит, что вы присвоили значение всем свойствам. Если забудете одно — снова ошибка.

class User {
  name: string;
  age: number;

  constructor(name: string) {
    this.name = name;
    // age не присвоен — ошибка!
  }
}

Это сделано специально: TypeScript защищает вас от случайного undefined. Вы либо инициализируйте все поля, либо явно разрешите им быть неопределёнными.

Способ №3: Оператор definite assignment (!)

Иногда вы точно знаете, что свойство будет инициализировано позже, но не в конструкторе. Например, через внедрение зависимостей в Angular или через вызов асинхронного метода. Тогда используйте definite assignment assertion — восклицательный знак после имени свойства.

class UserService {
  user!: User; // OK, я обещаю, что оно будет

  constructor() {
    this.initUser();
  }

  initUser() {
    this.user = new User('Alice', 30);
  }
}

Внимание: это мощный, но опасный инструмент. Вы говорите TypeScript: "Не проверяй, я знаю, что делаю". Если вы забудете инициализировать свойство — получите undefined в рантайме. Используйте только когда уверены на 100%.

Способ №4: Сделать свойство опциональным (?)

Если свойство действительно может отсутствовать — сделайте его опциональным. Добавьте знак вопроса к типу.

class User {
  name?: string; // OK, может быть undefined
  age?: number;
}

Теперь TypeScript понимает: name может быть string | undefined. Ошибка исчезает. Минус: каждый раз при обращении к свойству придётся проверять его на undefined.

Способ №5: Отключить strictPropertyInitialization

Самый радикальный, но наименее рекомендуемый способ. Просто выключите проверку в tsconfig.json.

{
  "compilerOptions": {
    "strict": true,
    "strictPropertyInitialization": false // ❌ Не делайте так бездумно
  }
}

Когда это оправдано: только если вы мигрируете старый JavaScript-код на TypeScript и не хотите сразу править сотни классов. Но в новом проекте лучше держать эту проверку включённой — она ловит реальные баги.

Реальный пример: Angular и сервисы

Чаще всего эту ошибку видят разработчики на Angular. Вот типичный сценарий:

@Injectable()
export class DataService {
  private data: any[]; // ❌ Ошибка!

  constructor() {
    // Ничего не присваиваем
  }

  loadData() {
    this.data = []; // Инициализация только при вызове метода
  }
}

Проблема: data не инициализировано в конструкторе, а метод loadData() может быть вызван не сразу. Решение — definite assignment или инициализация пустым массивом:

private data: any[] = []; // ✅ Лучшее решение

Сравнение всех подходов (шпаргалка)

  • Инициализация в объявлении — для значений по умолчанию. Самый безопасный.
  • Инициализация в конструкторе — для значений из параметров. Обязательный минимум.
  • Definite assignment (!) — для поздней инициализации. Используйте с осторожностью.
  • Опциональное свойство (?) — если значение действительно может отсутствовать.
  • Отключение проверки — только для миграции legacy-кода.

Какой способ выбрать? Практические советы

Вот простое правило: всегда начинайте с явной инициализации в объявлении или конструкторе. Это делает код предсказуемым и читаемым. Если это невозможно — используйте definite assertion. Если свойство опционально по смыслу — добавьте ?.

Помните: ошибка "Property has no initializer" — это не баг TypeScript, а ваша страховка от undefined в рантайме. Не отключайте её без веской причины.

Блоги

Рекомендаций по книгам