ハテナマークとビックリマークって?
Typescriptでたまによく出てくる記述
const foo = hoge?.huga
const bar = hoge!.huga
これのことです。
正式名称は、こちらがオプショナルチェイン
const foo = hoge?.huga // オプショナルチェイン
こっちが非nullアサーション
const bar = hoge!.huga // 非nullアサーション
というらしいです。
ハテナとかビックリとか調べても色んな場面で使われているので、
これらの名前をしっかり覚えておくと検索が容易になりますね。
簡単に言うと?
- hoge?.huga -> hoge が undefined なら undefined
- hoge!.huga -> hoge が undefined でないと仮定して強制実行
ちょっと詳しく
どちらも、変数hogeがundefinedである可能性がある時に使います。
しかし、似ている記述と用法でありながら、
動作は全くの真逆と言っても良い振る舞いをします。
例えば以下の例です。
class Hoge {
huga?: string
}
const hugaLength = () => {
return new Hoge().huga.length // エラー
}
hugaはundefinedである可能性があるために、
lengthにアクセスは出来ません。
この時に、オプショナルチェインまたは非nullアサーションを使用して、
エラーを修正できます。
オプショナルチェインの場合
使い方
オプショナルチェインとは、
チェインの途中の変数がundefinedだった場合に、
そこで処理を中断し、undefinedで返す式です。
オプショナルチェインでエラーを修正してみましょう。
以下のようになります。
class Hoge {
huga?: string
}
const hugaLength = (): number | undefined => {
return new Hoge().huga?.length
}
これでエラーが修正されました。
振る舞いを見ていきましょう。
オプショナルチェインの振る舞いを書き下すとこうなります。長ったらしいですね。
const hugaLength = (): number | undefined => {
const hoge = new Hoge()
if(hoge.huga===undefined){
return undefined
}
return hoge.huga.length
}
返却がlengthだけではなくなったことから、
関数hugaLengthの返り値の型にundefinedが追加されています。
どういう時に使う?
オプショナルチェインは、
長ったらしい存在確認を1つの記号で行ってくれる、
というものです。チェインの深い位置にあるプロパティを取得したいけど、
いちいち存在確認処理を書くのがめんどくさい場合に使います。
一種の糖衣構文と言えるでしょう。
非nullアサーションの場合
非nullアサーションは、確実に存在するということを、
プログラマの全責任において宣言するというものです。
非nullアサーションでエラーを修正してみましょう。
class Hoge {
huga?: string
}
const hugaLength = (): number => {
return new Hoge().huga!.length // 実行時エラー
}
これでエラーが修正されました。
ただし、見ての通りhugaには何も与えられてませんから、
huga.lengthは実行時にエラーになります。
これは、以下の式とほぼ等価です。
(エラー内容がundefinedアクセスとの違いはあります)
const hugaLength = (): number => {
const hoge = new Hoge()
if(hoge.huga===undefined){
throw new Error()
}
return hoge.huga.length
}
これだけだとコンパイラに嘘をついて通してもらっただけに見えますが、
利点もちゃんとあります。
オプショナルチェインや存在確認だと、どうしても返り値に
undefinedを含む必要性が出てくることがあります。
非nullアサーションで対応すると、返り値にundefinedを含む必要がなくなります。
返り値にundefinedが含まれなくなると、呼び出し側の存在確認処理がなくなり、
コードの流れがわかりやすくなります。
どういう時に使う?
コンパイラ側から見るとundefinedがあり得るが、
プログラマ観点だと絶対にundefinedになりえない場合に使います。
以下のコードは、通常の範囲内では、必ずhugaは存在します。
(コンストラクタで必ず与えられ、かつreadonlyであるため)
class Hoge {
readonly huga?: string
constructor(huga:string){
this.huga = huga
}
}
const hugaLength = (): number => {
return new Hoge("huga").huga.length // エラー
}
これをオプショナルチェインで修正すると、
const hugaLength = (): number|undefined => {
return new Hoge("huga").huga?.length
}
のように、返り値にundefinedが追加され、呼び出し側に負担がかかります。
こういった場合には、非nullアサーションを用いることで、
コードがすっきりとします。
運用方法
オプショナルチェインは、積極的に使うべきで、
非nullアサーションは積極的に使うべきではないと考えます。
オプショナルチェインは、コードの振る舞い自体を変更し、
エラーに対処します。なので、使用することにより、
TypeScriptの型付けの恩恵を受けることが出来ます。
非nullアサーションは、コンパイラの振る舞いを変更することで、
対処します。こうなると、Typescriptの恩恵を得ることができなくなります。
非nullアサーションを使う時は、
「値が必ず存在すること前提で、万一値が存在しなかった場合、エラーを投げるしか無い」、
といった状況のみで使うべきです。
この場合のみ、非nullアサーションでtypeErrorが起きるのと、エラーを投げるので、
実行内容自体は変わらないので、書き換えと考えることが出来るからです。
まとめ
オプショナルチェインは、undefinedチェック処理の糖衣構文
非nullアサーションは、undefinedエラー処理の糖衣構文