はいさい!オースティンやいびーん!
概要
TypescriptのstrictNonNullとstrictClassInitializersについて解説します。
strictNonNull
とは
TypeScriptは、本来、undefinedと null を完全に違う型として認識しています。
実際、JavaScriptでも undefined !== null
ですし、undefinedと null を大体同じだと勘違いして使っていると、痛い目に遭います(筆者はずっと誤解していました)。
しかし、TypeScriptの緩い設定だと、これらの違いについて文句を言わないようになっています。
strictNonNull
を有効にすると、Runtimeで絶対にエラーになるようなコードに気づくことができます。
そして、undefinedの可能性がある戻り値について、厳しく指摘も入るようになります。
以下のコードをみてみましょう。
const myLetters = ['a', 'b'];
const isOneLetter = myLetters.find(v => v === 'c').length === 1
myLettersのfindの戻り値を考えましょう。何の型になるでしょうか?
'c'が配列にないので、stringではなく、undefined になりますね。
undefinedには length はないので、JavaScriptだったらここでまず転けますね。
strictNonNullが有効だと、ここでTypeScriptはそれを教えてくれるのです。
そして以下のように対応します。
const myLetters = ['a', 'b'];
const isOneLetter = myLetters.find(v => v === 'c')?.length === 1 // findの戻り値がundefinedだったら、.lengthをGetせずに、undefinedをそこで返す
// OR
const isOneLetter = myLetters.find(v => v === 'c')!.length === 1 // エラーはそれでも起こる、危ない!
!
もしくは ?
を使って、TSにundefined
の可能性があるコードに対してどうするべきかを教える必要があります。これらの機能はNonNullAssertions
と言います。
strictPropertyInitializationとは
上記のstrictNonNullとセットで来るのは、strictPropertyInitializationの設定です。
以下のコードを見てみましょう。
class MyComponent extends OnInit {
items: Item[];
ngOnInit() {
this.items.forEach(item => item.viewed = true);
}
}
this.itemsの Propertyはどこでも設定されていないので、ngOnInitでエラーになります。なぜなら、this.itemsは Item[] ではなく、 undefinedだからです。
なので、ここでは二つ、対策を施します。
初期値を与える場合
class MyComponent extends OnInit {
items: Item[] = [];
ngOnInit() {
this.items.forEach(item => item.viewed = true);
}
}
絶対に定義されることをわかっている場合
class MyComponent extends OnInit {
@Input() items!: Item[];
ngOnInit() {
this.items.forEach(item => item.viewed = true);
}
}
##定義されない可能性がある場合
class MyComponent extends OnInit {
@Input() items?: Item[]; // TS上、Item[] | undefinedになるのです。
ngOnInit() {
this.items?.forEach(item => item.viewed = true);
}
}
上記の対策を施すと、いろんな場面で起きるエラーを未然に防ぐことができます。
まとめ
以上、strictNullChecksとstrictPropertyInitializerについての解説ですが、さらに知りたい方は、TypeScriptの正式ドキュメントをご覧ください!
また、勉強会を開いてもいいのですが、もし勉強会が役に立ちそうなら、オースティンまでご連絡ください。