definite assignment assertion
definite assignment assertionは、変数やプロパティが確実に初期化されていることをTypeScriptのコンパイラに伝える演算子です。
strictNullChecks
と変数の初期化エラー
TypeScriptはコンパイラオプションstrictNullChecks
がtrue
のとき、初期化されていない変数を参照した際にエラーを出します。
ts
letnum : number;Variable 'num' is used before being assigned.2454Variable 'num' is used before being assigned.console .log (* 2); num
ts
letnum : number;Variable 'num' is used before being assigned.2454Variable 'num' is used before being assigned.console .log (* 2); num
変数の初期化が明らかに関数内で行われている場合でも、コンパイラは変数が初期化されていないとエラーを出します。
ts
letnum : number;initNum (); // 関数内でnumを初期化しているが…Variable 'num' is used before being assigned.2454Variable 'num' is used before being assigned.console .log (* 2); num functioninitNum () {num = 2;}
ts
letnum : number;initNum (); // 関数内でnumを初期化しているが…Variable 'num' is used before being assigned.2454Variable 'num' is used before being assigned.console .log (* 2); num functioninitNum () {num = 2;}
strictPropertyInitialization
とプロパティの初期化エラー
TypeScriptでは次のコンパイラオプションの両方がtrue
のとき、クラスのプロパティが初期化されていないとエラーを出します。
ts
classFoo {Property 'num' has no initializer and is not definitely assigned in the constructor.2564Property 'num' has no initializer and is not definitely assigned in the constructor.: number; num }
ts
classFoo {Property 'num' has no initializer and is not definitely assigned in the constructor.2564Property 'num' has no initializer and is not definitely assigned in the constructor.: number; num }
TypeScriptコンパイラは、プロパティ定義またはconstructor
でプロパティが初期化されるかを見ています。しかし、constructor
以外のメソッドで初期化されるところまでは追いかけません。たとえば、次例のnum3
は実際は初期化されるものの、コンパイラは初期化がされていないと警告を出します。
ts
classFoo {num1 : number = 1; // 初期化しているnum2 : number;Property 'num3' has no initializer and is not definitely assigned in the constructor.2564Property 'num3' has no initializer and is not definitely assigned in the constructor.: number; num3 constructor() {this.num2 = 1; // 初期化しているthis.initNum3 (); // num3を初期化している}initNum3 () {this.num3 = 1;}}
ts
classFoo {num1 : number = 1; // 初期化しているnum2 : number;Property 'num3' has no initializer and is not definitely assigned in the constructor.2564Property 'num3' has no initializer and is not definitely assigned in the constructor.: number; num3 constructor() {this.num2 = 1; // 初期化しているthis.initNum3 (); // num3を初期化している}initNum3 () {this.num3 = 1;}}
definite assignment assertionを使う
変数やプロパティの初期化が確実に行われていることをコンパイラに伝えるには、definite assignment assertionを使います。変数宣言の変数名やプロパティ名のあとに!
を書きます。
ts
letnum !: number;// ^definite assignment assertioninitNum ();console .log (num * 2); // エラーにならないfunctioninitNum () {num = 2;}
ts
letnum !: number;// ^definite assignment assertioninitNum ();console .log (num * 2); // エラーにならないfunctioninitNum () {num = 2;}
ts
classFoo {num !: number;// ^definite assignment assertion}
ts
classFoo {num !: number;// ^definite assignment assertion}
definite assignment assertionの意味は「確実な代入の表明」です。この変数はこの型であることが確実であることをTypeScriptコンパイラに伝えるということです。
非Nullアサーション
別の方法として、非Nullアサーション(non-null assertion)を使う方法もあります。この場合は、変数を参照するコードにて、変数のあとに!
を書きます。
ts
letnum : number;initNum ();console .log (num ! * 2); // エラーにならない// ^非NullアサーションfunctioninitNum () {num = 2;}
ts
letnum : number;initNum ();console .log (num ! * 2); // エラーにならない// ^非NullアサーションfunctioninitNum () {num = 2;}
より安全なコードを書くには
definite assignment assertionと非Nullアサーションは、型の安全性を保証する責任をコンパイラからプログラマに移すものです。そして、型に関してはコンパイラより人間のほうがミスをしやすいです。なので、こうしたアサーションはできる限り使わないほうが安全性は高いです。
たとえば、上の例であればinitNum
の戻り値をnum
に代入するほうが、より安全なコードになります。
ts
letnum : number;num =initNum ();console .log (num ! * 2);functioninitNum () {return 2;}
ts
letnum : number;num =initNum ();console .log (num ! * 2);functioninitNum () {return 2;}
他にも、num
が数値型であるかを型ガードでチェックする方法もあります。
ts
letnum : number | undefined;initNum ();// 型ガードif (typeofnum === "number") {console .log (num * 2);}functioninitNum () {num = 2;}
ts
letnum : number | undefined;initNum ();// 型ガードif (typeofnum === "number") {console .log (num * 2);}functioninitNum () {num = 2;}
このようにアサーションに頼らない方法はないかを先に検討することをお勧めします。その上で、どうしてもというときにアサーションを使うようにしましょう。フレームワークやライブラリの都合で、やむを得ない場合もあります。
学びをシェアする
・definite assignment assertionは、変数初期化が確実であるとTypeScriptのコンパイラに伝える
・変数名のあとに!を書く
・型安全の責任をコンパイラからプログラマに移すものなので、使わない方法を先に検討する
・どうしようもないときに使う
『サバイバルTypeScript』より