はじめに
TypeScriptでは、変数の後ろに「!」を置くことで、その変数がnull
またはundefined
ではない、と宣言する記法があります。
これはnon-null assertion(非nullアサーション) と呼ばれます。
一般的に非推奨とされていますが、用意されているからには使い所があるはずです。
どのような場面で使うべきなのかを考えます。
「!」を変数の後ろに置く記法として「definite assignment assertion(明確な割り当てアサーション)」もありますが、本記事ではこちらは扱いません。
non-null assertionとは
概要
null
またはundefined
が設定される可能性のある変数に対して、末尾につける「!」がnon-null assertionと呼ばれます。
これはTypeScript独自の記法であり、JavaScriptには存在しません。
TypeScriptからJavaScriptに変換する際、「!」は削除されます。
使用例
non-null assertionは以下のような場合に使用します。
未使用時の例
interface User {
name: string;
age: number;
gender?: string;
}
const func = (user: User) => {
const gender = user.gender.toUpperCase();
console.log(gender);
};
任意項目であるgender
に対して、toUpperCase
メソッドを呼び出しています。
VS Code上では以下のようにエラーが発生します。
この処理のように、null
またはundefined
が設定される可能性のある変数に対して、何らかの処理を呼び出す場合、TypeScript上ではエラーになります。
13行目で確実にgender
を設定していますが、コンパイルの際には値の設定有無の判定まではできません。
使用時の例
この変数に対して「!」をつけることで、「確実に値が設定される」ということをassertion(主張/宣言)し、エラーを抑止するのがこの記法の役割です。
interface User {
name: string;
age: number;
gender?: string;
}
const func = (user: User) => {
// genderの末尾に!を設定
const gender = user.gender!.toUpperCase();
console.log(gender);
};
しかし、この記法は値が入ることを保証しません。
あくまでも宣言であるので、実際にどうなるかは実装次第です。
現に、12行目ではgender
を設定していませんが、この時点ではエラーかどうかの判定はできません。
実行時にエラーとなります。
non-null assertionの使い所
使うべきでないパターン
基本的に、多くの場合使用するべきではありません。
なぜなら、バグの温床となる可能性があるからです。
「多くの場合」というのは抽象的すぎるので、より具体的に表現すると、「TypeScriptの実装の範疇である場合」には使用するべきではないと考えています。
しかし、使うべきではないとはいえ、発生しているエラーは解消しなければなりません。
使用しない場合のエラー回避の方法を確認していきます。
エラーを回避する:型ガードでチェックする
一番手っ取り早いのはこの方法です。
const func = (user: User) => {
if (typeof user.gender === 'string') {
const gender = user.gender.toUpperCase();
console.log(gender);
}
};
func({ name: "John", age: 28 });
gender
の型はstring
とundefined
のユニオン型なので、string
型のときのみ処理を実行するようにします。
このように、条件分岐を書くことでエラーを回避するのが一番確実で早いです。
ほかにはオプショナルチェーンを使ったり、null合体演算子を使うのもよいかと思います。
エラーを回避する:型の設計を見直す
「!」はnull
またはundefined
でないことを宣言するものです。
裏を返せば、「!」を使っている状況というのは、確実に値が設定されていることがわかっているにも関わらず、変数の型にnull
またはundefined
が含まれている、ということかと思います。
値が設定されるとわかっているのであれば、そもそも元の変数の型を適切なものに見直すことで、「!」を使う状況自体をなくしてしまうのも方法の一つです。
使っても良いパターン
「TypeScriptの実装の範疇である場合」には使うべきではない、と書きました。
では、「実装の範疇外」であれば使っても良いことになりますが、これはどのようなパターンが考えられるでしょうか
DOM要素の操作
HTMLはTypeScriptの実装とは別の部分になるので、値が存在するかどうかをTypeScriptで検知することはできません。
しかし、確実にHTML上に当該要素があると分かっている場合には、non-null assertionを使うことで、シンプルに実装することができます。
const button = document.querySelector('#submit-button')!;
button.addEventListener('click', handleClick);
条件分岐を使って値の有無をチェックすることで、「!」の使用を回避することもできますが、冗長になる場合には、このように書くのが有効な場合もあります。
外部ライブラリとの連携
外部ライブラリの型定義が不完全で、実際には値が存在すると分かっている場合にも使ってよいかと思います。
import { someFunction } from 'external-library';
const result = someFunction().someProperty!;
外部ライブラリの場合には、自身の実装の範疇を超えているので、このように使用することでシンプルに書くことができます。
まとめ
自分が携わっている開発の現場で「!」をよく見かけるのですが、個人的にあまり好きではなく、どのような場合に使うべきなのかについて、考えてみました。
公式サイトにおいてnon-null assertionについて言及されている箇所が多くなく、またベストプラクティスも見つけられなかったので、不正確な部分もあるかもしれません。
ただ、やはり基本的には極力使うべきではなく、自分の実装が及ばない部分にのみ限定して使うべきなのでは、と考えています。