Флаг strictPropertyInitialization в tsconfig.json заставляет TypeScript выдавать ошибки, если свойства класса не инициализированы при построении. Исправить это можно тремя способами: задать значение прямо в объявлении, инициализировать в конструкторе, или поставить оператор утверждения ! — если уверен, что свойство будет присвоено в рантайме.

Например, у нас есть класс Books с одним свойством titles, его тип - массив строк. Под нашим классом Books мы создаем новый экземпляр этого класса через new.

class Books {
  titles: string[];

  constructor() {}
}
const books = new Books();

const shortTitles = books.titles.filter(
  title => title.length < 5
);

В этом примере, очевидно, что массив titles не был инициализирован и он неопределенен undefined.

Однако, давай предположим, что наше приложение намного сложнее, и где-то ещё в кодовой базе мы пытаемся использовать экземпляр Books, чтобы получить из него заголовки, а затем отфильтровать их.

Если мы откроем терминал (командную строку) и попытаемся запустить сгенерированный JavaScript, то получим ошибку Cannot read property 'filter' of undefined.

О том, как настроить TypeScript локально — в посте Базовая настройка TypeScript-проекта.

В TypeScript 2.7 появился новый флаг strictPropertyInitialization, который предупредит нас об этих типах проблем во время компиляции. Чтобы это работало, нам также нужно включить флаг strictNullChecks. Добавляем это в файл tsconfig.json:

{
  "compilerOptions": {
    "strictPropertyInitialization": true,
    "strictNullChecks": true
  }
}

strictPropertyInitialization — включает строгую проверку инициализации свойств в классах. strictNullChecks — включает строгие проверки на null и undefined.

Также, можно включить все строгие проверки, просто добавив флаг strict. Он включит все строгие опции проверки типов.

{
  "compilerOptions": {
    "strict": true
  }
}

Вернувшись к файлу, увидим ошибку на titles. TypeScript теперь различает string[] и undefined — и видит, что titles может оказаться неопределённым.

class Books {
  titles: string[]; // string[] !== undefined

  constructor() {}
}

Поскольку TypeScript нигде не видит инициализацию titles, он выдает ошибку - Property 'titles' has no initializer and is not definitely assigned in the constructor.. Вот почему нам нужно включить флаг strictNullChecks - он позволит разделить undefined, null и все другие типы.

Для простого исправления можем добавить | undefined, к объявлению типа.

class Books {
  titles: string[] | undefined;

  constructor() {}
}

Теперь ошибка переместилась в books.titles, поскольку, мы сказали что titles могут быть undefined. Чтобы это исправить, нам нужно сделать проверку на то, что titles определен. Например, обернуть его в if блок.

class Books {
  titles: string[] | undefined;

  constructor() {}
}
const books = new Books();

if (books.titles) {
  const shortTitles = books.titles.filter(title => title.length < 5);
}

Всё отлично работает! Включив этот флаг, мы защищены от потенциальных ошибок во время выполнения, но, добавление проверок на undefined везде, где мы используем экземпляры этого класса, может стать громоздким. Другой вариант - инициализировать свойство непосредственно в классе значением по умолчанию. Убираем undefined, так как знаем, что titles будет инициализирован с самого начала.

class Books {
  titles: string[] = ["Eloquent JavaScript", "JavaScript: The Good Parts"];

  constructor() {}
}
const books = new Books();

const shortTitles = books.titles.filter(title => title.length < 5);

Мы также можем инициализировать titles в constructor(). Это также устранит ошибку, так как мы по-прежнему гарантируем, что после инициализации все свойства этого класса будут определены.

class Books {
  titles: string[];

  constructor() {
    this.titles = ["Eloquent JavaScript", "JavaScript: The Good Parts"];
  }
}

// или более лаконичный синтаксис через `public`

class Books {
  constructor(
    public titles: string[] =
      ["Eloquent JavaScript", "JavaScript: The Good Parts"]
  ) {}
}

Также, мы можем использовать библиотеку внедрения зависимостей, которая, как мы знаем, инициализирует свойства всех этих классов во время выполнения. В этом случае можем указать восклицательный знак !, после имени свойства. Это называется оператор утверждения определённого присваивания (definite assignment assertion operator). Это своего рода способ сообщить TypeScript, что данное свойство будет инициализировано.

class Books {
  titles!: string[];

  constructor() {}
}

Имей в виду, что теперь мы вернулись к исходной точке в отношении защиты во время компиляции, которую получаем из TypeScript. books.titles.filter всё ещё выдаст ошибку. Тем не менее, добавив ! явно для каждого свойства, заставит разработчиков задуматься о небезопасном использовании добавляемых ими свойств.

Итог

Флаг strictPropertyInitialization даёт больше уверенности при работе со свойствами экземпляров и устраняет целый класс ошибок времени выполнения.

После его включения свойства класса нужно явно инициализировать: напрямую, в конструкторе или через оператор утверждения !.

class Books {
  titles!: string[];
  language: string = "JavaScript";
  isLoading: boolean;

  constructor() {
    this.isLoading = true;
  }
}