undefined - TypeScriptキーワード一覧
スポンサーリンク
キーワードとしての「undefined」は「undefined 値」のみを受け付ける「undefined 型」として用いられます。
概要
(特筆の無いものは TypeScript/spec.md 「3.2.7 The Undefined Type」での定義によります。
- (型としての)キーワード「undefined」(または「undefined 型」)はJavaScript(ECMAScript)における同じ名前のプリミティブ型、すなわち『Undefined 型』を表します。
- 「undefined 値」は「undefined 型」の取ることができる唯一の値です。
- 「undefined 値」は未初期化の変数、オブジェクトに定義されていないプロパティーの値、「グローバルオブジェクトの『undefined』という名前のプロパティー」の値、戻り値を返さなかった関数の戻り値、および「void 演算子」の結果などにより得られます(いずれもES2015の仕様)。
- 「undefined 型」はすべての型の部分型(subtype)となり、そのデータ(= 「undefined 値」)はすべての変数や引数に代入可能です。
- ただし、「null 型」の定義から、「null 型」への「undefined 値」の代入や、「undefined 型」への「null 値」の代入はできません。
- また、「strictNullCheck」が設定されている場合を除きます(後述)。
- コンパイラーオプション「strictNullCheck」が有効である場合、「undefined 型」は他の型に属さない独立した型となり、その値は「undefined 型」と「any 型」にのみ代入可能となります。(TypeScript PR #7140)
x
、!!x
、x != null
、x !== void 0
、およびtypeof x !== 'undefined'
などという構文を用いると、その条件(型ガード; type guard)下で実行されるコードにおいて x から undefined 型が除外されます。(この x のような単純な変数参照だけでなく、p.q
のようなプロパティー参照などでも適用されます。)x!
などのように「!」という後置演算子を用いると、その式において x から undefined 型(および null 型)が除外されたものとして扱います(Non-null アサーション、実際に実行時チェックされるわけではないのでご注意ください)。
キーワード情報
- 「undefined」は識別子として予約されていません。(ユーザー定義の識別子として利用できますが、ES2015の仕様でグローバル空間(グローバルオブジェクト)に「undefined」が定義されるので重複はできません。)
- 「undefined」は型名として予約されていません。(ユーザー定義の型名として利用できます。)
- ただし、グローバル空間ではグローバルオブジェクトのプロパティーである「undefined」と競合するため使用できません。
- また、それ以外の空間で「undefined」という型名を定義しても、その空間(名前空間など)を明示的に示すような指定が無い場合、「undefined」という型名を参照するとTypeScript組み込みの「undefined 型」が使用されます。(2018年8月時点で仕様に明記されていない模様であり、混乱の元になるので「undefined」というユーザー定義型を作るのは避けるべきです。)
詳細
「undefined」はその名の通り「undefined 値」を取り扱う際に用いるキーワードです。ECMAScript(JavaScript)ではグローバルオブジェクトに「undefined 値」を保持するプロパティー「undefined」が定義されていますが、TypeScriptでは型名としては「undefined 型」を示すキーワードとして使用されます。
※ 「undefined」は予約語ではなく、グローバル空間で「undefined」という値が利用できるのはあくまで「グローバルオブジェクトにあるプロパティーの1つ」に「undefined」が存在するだけであるため、「undefined」という名前を引数名などに用いることもできます(混乱を招く可能性があるため避けるべきではあります)。また、確実な「undefined 値」を利用したい場合は「void」演算子を、「undefined 値」かどうかを判定したい場合は「typeof x === 'undefined'
」という構文を用いることができます。
「undefined 型」は、明示的に宣言された場合は「undefined 値」のみを設定することができる型となります。
let x: undefined; // undefined 型変数
x = void 0; // 「void 演算子」の結果として得られる undefined 値は undefined 型変数に代入可能
x = 10; // ERROR: TS2322: 数値を「undefined 型」に代入することができない
「strictNullCheck」が有効になっていない場合、「undefined 型」は「null 型」を除くあらゆる型の部分型の扱いになるため、「undefined 型」以外の型とunion type(共用体型)を作ると「undefined 型」の意味が失われます。逆にintersection type(交差型)を作ると「undefined 型」になります。
// 「strictNullCheck」が無効のとき
let y: number | undefined; // number 型変数
let z: number & undefined; // undefined 型変数
「undefined 型」が特に重要になるのは「strictNullCheck」が有効な場合です。このとき、「undefined 型」は他のあらゆる型と独立したような存在となります。
// 「strictNullCheck」が有効のとき
let y: number | undefined; // number | undefined 型変数
let z: number & undefined; // number & undefined 型変数(実質 never に近い型となり、このような記述をすることはまず無い)
また「strictNullCheck」が有効な場合、「undefined 型」とunion type(共用体型)を形成している型のデータに対してプロパティー参照や関数呼び出しなどを行おうとすると、「undefined の可能性がある」という旨の専用のエラーが発生します。
// 「strictNullCheck」が有効のとき
let fn: (() => void) | undefined = makeFunction2(); // (() => void) | undefined 型変数
console.log(fn.toString()); // ERROR: TS2532: undefined の可能性があるオブジェクト
fn(); // ERROR: TS2722: undefined の可能性がある関数の呼び出し
そのため、「undefined 型」を含む型のデータを扱う場合は、型ガード(type guard)などの型推論を用いて undefined を除去してから具体的な処理を行うのが基本となります。
// 「strictNullCheck」が有効のとき
let fn: (() => void) | undefined = makeFunction2(); // (() => void) | undefined 型変数
// undefined の場合は実行されないような条件文(型ガードになる)
if (fn !== void 0) {
// この中では「fn」は「() => void」型となるので、次の2行がコンパイル時エラーにならない
console.log(fn.toString());
fn();
}
// 渡した配列と同じ要素数の配列(中身は数値または undefined)を返す何かしらの関数を呼び出す
let arr: Array<number | undefined> = queryIdFromName(['edamame', 'natto', 'tofu']);
// 「arr」には「undefined 値」が含まれる可能性があるので、「undefined 値」を除去するために filter メソッドを用いる
let arrFiltered = arr.filter(
// Array の filter メソッドのオーバーロードに
// (ユーザー定義の)「型ガード関数」を受け付けるものがあるため、
// それを用いて「undefined 値」の除去(val !== void 0 で判定)と
// 「undefined 型」の除去(「x is Y」の「Y」にあたる型が
// 配列の要素の型になる)の両方を行うことができる
// ※ 「typeof val」が「number | undefined」と推論されるので
// 「Exclude<typeof val, undefined>」は「number」となる
// (TypeScript 2.8 以降で有効)
(val): val is Exclude<typeof val, undefined> => (val !== void 0)
);
// 「arrFiltered」は「number[]」型となるので、「arrFiltered」に対して「undefined 値」を気にする必要がなくなる
なお、(値としての)「undefined」は真偽値としては false 扱い・「!(void 0)
」の計算結果が「true」になることから、上記の「if (fn !== void 0) { ... }
」は単純に「if (fn) { ... }
」と書いても「undefined 型」を取り除く型ガードとして成立します。また、「if (fn !== void 0) { ... }
」は全く同じ意味で「if (typeof fn !== 'undefined') { ... }
」と書くこともできます。
※ 「false」扱いとなる値は「undefined 値」以外にもあるため、「if (x) { ... }
」という記法が常に良いとは限りません。例えば「x: string | undefined
」という変数に対して「undefined 値」を除去するつもりで「if (x) { ... }
」とすると、x が空文字列(''
)のとき if 文の中に入らなくなるため、空文字列が何かしらの意味を持つ値である場合は注意が必要です。
さらに、null 型にはないポイントとして、「省略可能な引数・プロパティー(メンバー)」を示すための「?」が識別子の直後に付加されている場合、その識別子は自動的に「undefined 型」と元の型のunion typeとして取り扱われます。(例: 「arg?: number
」の場合「arg
」は「number | undefined
」型)
// 「strictNullCheck」が有効のとき
// 「SomeObj」内の x, y, z はいずれも「number | undefined」型となる
// ※ ただし以下の定義の場合 z は「省略不可」となる(z を undefined とする場合は
// 明示的に「undefined 値」の指定が必要)
interface SomeObj {
x?: number;
y?: number | undefined;
z: number | undefined;
}
// 「SomeObjRequired」内の x, y はいずれも「number」型となるが、z は「number | undefined」型となる
// (「省略可能」のマークがついたもののみ、Required によって「undefined」が外れる)
type SomeObjRequired = Required<SomeObj>;
※ 明示的に「undefined」の型が指定されていない場合でも、「?」が付加されている引数/プロパティーは「undefined 型」を持つ扱いになるため、それらに「undefined 値」を明示的に指定することが可能です。
また、型推論できないものの、実行環境の制約やオーバーロード関数の実装における条件分岐、直前の処理の結果などで「undefined 値(および null 値)ではない」ことが明らかである場合は、TypeScriptに用意された後置演算子「!」を用いると「undefined 型」(と「null 型」)を強制的に除去することができます。
// 「strictNullCheck」が有効のとき
// someFunc で使用するデータ
const someMap: { [key: string]: string; } = {};
// オーバーロード関数の宣言
function setSomeValue(obj: { key: string, value: string }): void;
function setSomeValue(key: string, value: string): void;
// オーバーロード関数の実装
// (2つのオーバーロードで共通となる引数で実装する必要がある)
// ※ この実装にある引数パターンはオーバーロードの1つとしては含まれない
function setSomeValue(arg1: string | { key: string, value: string }, arg2?: string): void {
if (typeof arg1 === 'string') {
// arg1 が string であるとき、2番目のオーバーロードが呼び出されているはずなので
// arg2 は undefined ではないと見なし「!」を付加する
someMap[arg1] = arg2!;
} else {
// arg1 が string でないときは「key」と「value」のプロパティーを持つ
someMap[arg1.key] = arg1.value;
}
}
「null 型」のときと同様、「undefined 型」を除去する目的で「!」を使うのは「undefined ではないことが自明である」「undefined であることは完全に想定外なので実行時エラーとなっても良い」といったような場合に限るべきであり、また必要に応じてそのことが分かるようなコメントをソースコード上に記述しておくと良いと思われます。
このように「undefined 型」(および「null 型」)と「strictNullCheck」コンパイラーオプションを組み合わせることで、「undefined の可能性がある」ということをコンパイル時点でチェックし、undefined かどうかにより気を付けることができるため、うまく記述することで「undefined チェックをし忘れて undefined 参照によるエラー」というある種のヒューマンエラーを防止することに役立てることができます。
※ インデックス範囲外等で生じる「undefined 値」は現時点で「undefined 型」として現れない(推論されない)ため、インデックス値や任意プロパティーを扱うような処理では引き続き注意が必要です。