明確な割り当てアサーション(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;}}
明確な割り当てアサーションを使う
変数やプロパティの初期化が確実に行われていることをコンパイラに伝えるには、明確な割り当てアサーションを使います。変数宣言の変数名やプロパティ名のあとに!
を書きます。
ts
letnum !: number;// ^明確な割り当てアサーションinitNum ();console .log (num * 2); // エラーにならないfunctioninitNum () {num = 2;}
ts
letnum !: number;// ^明確な割り当てアサーションinitNum ();console .log (num * 2); // エラーにならないfunctioninitNum () {num = 2;}
ts
classFoo {num !: number;// ^明確な割り当てアサーション}
ts
classFoo {num !: number;// ^明確な割り当てアサーション}
非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;}
より安全なコードを書くには
明確な割り当てアサーションと非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
がnumber型であるかを型ガードでチェックする方法もあります。
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;}
このようにアサーションに頼らない方法はないかを先に検討することをお勧めします。その上で、どうしてもというときにアサーションを使うようにしましょう。フレームワークやライブラリの都合で、やむを得ない場合もあります。
学びをシェアする
・明確な割り当てアサーションは、変数初期化が確実であるとTypeScriptのコンパイラに伝える
・変数名のあとに!を書く
・型安全の責任をコンパイラからプログラマに移すものなので、使わない方法を先に検討する
・どうしようもないときに使う
『サバイバルTypeScript』より
関連情報
📄️ strictNullChecks
null・undefinedのチェックを厳しくする
📄️ strictPropertyInitialization
クラスプロパティの初期化を必須にする