Definite Assignment Assertion Modifier

Поля класса или локальные переменные, которые по сценарию могут иметь значение undefined, способны обрушить ожидаемый ход программы. Неудивительно, что TypeScript реализует механизмы, защищающие от подобных случаев. Но, кроме того, он также реализует механизмы, позволяющие обходить предыдущие, когда поведение лишь похоже на нежелательное по архитектурным причинам. Если кажется не понятным, то не беда, ведь данная глава и призвана помочь в этом разобраться.

Модификатор утверждения не принадлежности значения к типу undefined

Для повышения типобезопасности программы, рекомендуется вести разработку с активной опцией --strict (глава “Опции компилятора”), активирующей множество других опций изменяющих поведение компилятора и тем самым заставляя разработчиков писать код, сводящий к минимуму ошибки на этапе выполнения.

Это привело к созданию опции --strictPropertyInitialization, которая, при активной опции --strictNullChecks, запрещает классу иметь поля, типы которых явно не принадлежат к undefined и которые не были инициализированы в момент его создания. Таким образом предотвращается обращение к полям которые могут иметь значение undefined.

ts
class Identifier { public a: number = 0; // Ok, инициализация при объявлении public b: number; // Ok, инициализация в конструкторе public c: number | undefined; // Ok, явное указание принадлежности к типу Undefined public d: number; // Error, инициализация отсутствует constructor() { this.b = 0; } }

Но бывают случаи, при которых условия, устанавливаемые опцией --strictPropertyInitialization, не могут быть удовлетворены в полной мере. К самым распространенным случаям можно отнести установку значений полей с помощью DI (dependency injection), инициализации, вынесенной в методы жизненного цикла (life cycle), а также методы инициализации выполняемые из конструктора класса.

ts
// инициализация с помощью DI class A { @Inject(Symbol.for('key')) public field: number; // Error }
ts
// метод жизненного цикла из Angular class B { private field: number; // Error public ngOnInit(): void { this.field = 0; } }
ts
// инициализация вне конструктора class C { private field: number; // Error constructor(){ this.init(); } private init(): void { this.field = 0; } }

Для таких случаев синтаксис TypeScript содержит модификатор definite assignment assertion modifier, который указывается с помощью символа восклицательного знака (!), располагаемого после идентификатора поля или переменной.

ts
class Identifier { public identifier!: Type; } // или const identifier!: Type;

Применяя модификатор definite assignment assertion modifier, разработчик сообщает компилятору, что берет ответственность за инициализацию поля на себя.

ts
// инициализация с помощью DI class A { @Inject(Symbol.for('key')) public field!: number; // Ok }
ts
// метод жизненного цикла из Angular class B { private field!: number; // Ok public ngOnInit(): void { this.field = 0; } }
ts
// инициализация вне конструктора class C { private field!: number; // Ok constructor(){ this.init(); } private init(): void { this.field = 0; } }

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