null - TypeScriptキーワード一覧
スポンサーリンク
「null」キーワードは以下の2つの意味で使用されます。
- 「null 値」 : ECMAScriptの仕様で「null」という名前は「Null 型の値」、すなわち「null 値」を表します。
- 「null 型」 : 「null 値」のみを受け付ける型を表します。
いずれも「null 値」に関するものであるため、このページでは両者について説明しています。
概要
「null」キーワードのうち「null 値」を表す識別子「null」は、ECMAScriptの仕様で以下のように定義されています。
- 「Null 型」とは唯一「null 値」をとる型である (refs. ES2015 - 4.3.13 Null type) / Null 型は厳密に「null」と呼ばれる値を持つ (refs. ES2015 - 6.1.2 The Null Type)
- 「null」という識別子は「null 値」として評価される (refs. ES2015 - 12.2.4.1 Runtime Semantics: Evaluation)
一方型名としての「null」は以下のようにされています。(特筆の無いものは TypeScript/spec.md 「3.2.6 The Null Type」での定義によります。
- (型としての)キーワード「null」(または「null 型」)はJavaScript(ECMAScript)における同じ名前のプリミティブ型、すなわち『Null 型』を表し、識別子「null」の型となります。
- 「null 値」は「null 型」の取ることができる唯一の値です。すなわち、「null 型」には「null 値」のみが代入可能です。
- 「null 型」は「undefined 型」を除くすべての型の部分型(subtype)となり、そのデータ(= 「null 値」)は「undefined 型」を除くすべての変数や引数に代入可能です。
- ただし「strictNullCheck」が設定されている場合を除きます(後述)。
- コンパイラーオプション「strictNullCheck」が有効である場合、「null 型」は他の型に属さない独立した型となり、その値は「null 型」と「any 型」にのみ代入可能となります。(TypeScript PR #7140)
x
、!!x
、x != null
、およびx !== null
などという構文を用いると、その条件(型ガード; type guard)下で実行されるコードにおいて x から null 型が除外されます。(この x のような単純な変数参照だけでなく、p.q
のようなプロパティー参照などでも型ガード(type guard)が利用できます。)x!
などのように「!」という後置演算子を用いると、その式において x から null 型(および undefined 型)が除外されたものとして扱います(Non-null アサーション、実際に実行時チェックされるわけではないのでご注意ください)。
キーワード情報
- 「null」は識別子として予約されています。(ES2015の仕様で「予約語」に含まれ、ユーザー定義の識別子として利用できません。)
- 「識別子として」予約されているだけであるため、プロパティーキー名としては利用可能です。
- 「識別子として」予約されているため、ユーザー定義の型名としても利用できません。(TypeScriptの文法上、
type
およびinterface
などで定義する際に用いる名前(識別子)は BindingIdentifier (ES2015の文法を継承)であり、BindingIdentifier は「予約語」を禁止しているため。refs. TypeScript/spec.md 「A.1 Types」・「A.5 Interfaces」・「A.6 Classes」など, ES2015 - 12.1 Identifiers)
詳細
「null」はその名の通り「null 値」を取り扱う際に用いるキーワードです。ECMAScript(JavaScript)では「null 値」を明示的に表すキーワードとして定義されていますが、TypeScriptではそれに加えて「null 型」を示すキーワードとしても使用されます。
「null 型」は、明示的に宣言された場合は「null 値」のみを設定することができる型となります。
let x: null; // null 型変数
x = null; // null 値は null 型変数に代入可能
x = 10; // ERROR: TS2322: 数値を「null 型」に代入することができない
「strictNullCheck」が有効になっていない場合、「null 型」は「undefined 型」を除くあらゆる型の部分型の扱いになるため、「undefined 型」以外の型とunion type(共用体型)を作ると「null 型」の意味が失われます。逆にintersection type(交差型)を作ると「null 型」になります。
// 「strictNullCheck」が無効のとき
let y: number | null; // number 型変数
let z: number & null; // null 型変数
「null 型」が特に重要になるのは「strictNullCheck」が有効な場合です。このとき、「null 型」は他のあらゆる型と独立したような存在となります。
// 「strictNullCheck」が有効のとき
let y: number | null; // number | null 型変数
let z: number & null; // number & null 型変数(実質 never に近い型となり、このような記述をすることはまず無い)
また「strictNullCheck」が有効な場合、「null 型」とunion type(共用体型)を形成している型のデータに対してプロパティー参照や関数呼び出しなどを行おうとすると、「null の可能性がある」という旨の専用のエラーが発生します。
// 「strictNullCheck」が有効のとき
let fn: (() => void) | null = makeFunction(); // (() => void) | null 型変数
console.log(fn.toString()); // ERROR: TS2531: null の可能性があるオブジェクト
fn(); // ERROR: TS2721: null の可能性がある関数の呼び出し
そのため、「null 型」を含む型のデータを扱う場合は、型ガードなどの型推論を用いて null を除去してから具体的な処理を行うのが基本となります。
// 「strictNullCheck」が有効のとき
let fn: (() => void) | null = makeFunction(); // (() => void) | null 型変数
// null の場合は実行されないような条件文(型ガードになる)
if (fn !== null) {
// この中では「fn」は「() => void」型となるので、次の2行がコンパイル時エラーにならない
console.log(fn.toString());
fn();
}
// 渡した配列と同じ要素数の配列(中身は文字列または null)を返す何かしらの関数を呼び出す
let arr: Array<string | null> = queryNamesFromId([101, 106, 129]);
// 「arr」には「null 値」が含まれる可能性があるので、「null 値」を除去するために filter メソッドを用いる
let arrFiltered = arr.filter(
// Array の filter メソッドのオーバーロードに
// (ユーザー定義の)「型ガード関数」を受け付けるものがあるため、
// それを用いて「null 値」の除去(val !== null で判定)と
// 「null 型」の除去(「x is Y」の「Y」にあたる型が
// 配列の要素の型になる)の両方を行うことができる
// ※ 「typeof val」が「string | null」と推論されるので
// 「Exclude<typeof val, null>」は「string」となる
// (TypeScript 2.8 以降で有効)
(val): val is Exclude<typeof val, null> => (val !== null)
);
// 「arrFiltered」は「string[]」型となるので、「arrFiltered」に対して「null 値」を気にする必要がなくなる
なお、(値としての)「null」は真偽値としては false 扱い・「!null
」の計算結果が「true」になることから、上記の「if (fn !== null) { ... }
」は単純に「if (fn) { ... }
」と書いても「null 型」を取り除く型ガードとして成立します。
※ 「false」扱いとなる値は「null 値」以外にもあるため、「if (x) { ... }
」という記法が常に良いとは限りません。例えば「x: number | null
」という変数に対して「null 値」を除去するつもりで「if (x) { ... }
」とすると、x が 0 のとき if 文の中に入らなくなるため、「0」が何かしらの意味を持つ値である場合は注意が必要です。
また、型推論できないものの、実行環境の制約や直前の処理の結果などで「null 値(および undefined 値)ではない」ことが明らかである場合は、TypeScriptに用意された後置演算子「!」を用いると「null 型」(と「undefined 型」)を強制的に除去することができます。
// 「strictNullCheck」が有効のとき
// 何かしらのDOM要素を生成する関数を呼び出す
const someElement = createSomeElement();
// 「app」というIDを持つDOM要素を取得する
// (「appElement」は「HTMLElement | null」型になる)
const appElement = document.getElementById('app');
// 「このスクリプトは『app』というIDが必ず存在する状況下で実行される」として
// 「appElement」の型に「null 型」が無いものとするために「!」を付ける
appElement!.insertBefore(someElement, appElement!.firstChild);
※ 上記の例の場合は、以下のように「!」を付加する位置を「getElementById(...)」にすることもできます。
// 「strictNullCheck」が有効のとき
// 何かしらのDOM要素を生成する関数を呼び出す
const someElement = createSomeElement();
// 「app」というIDを持つDOM要素を取得する
// 「このスクリプトは『app』というIDが必ず存在する状況下で実行される」として
// 「appElement」の型に「null 型」が無いものとするために「!」を付ける
// (「appElement」は「null 型」が除去され「HTMLElement」型になる)
const appElement = document.getElementById('app')!;
appElement.insertBefore(someElement, appElement.firstChild);
「null 型」を除去する目的で「!」を使うのは「null ではないことが自明である」「null であることは完全に想定外なので実行時エラーとなっても良い」といったような場合に限るべきであり、また必要に応じてそのことが分かるようなコメントをソースコード上に記述しておくと良いと思われます。
このように「null 型」(および「undefined 型」)と「strictNullCheck」コンパイラーオプションを組み合わせることで、「null の可能性がある」ということをコンパイル時点でチェックし、null かどうかにより気を付けることができるため、うまく記述することで「null チェックをし忘れて null 参照エラー」というある種のヒューマンエラーを防止することに役立てることができます。