Programming Field

undefined - TypeScriptキーワード一覧

スポンサーリンク

キーワードとしての「undefined」は「undefined 値」のみを受け付ける「undefined 型」として用いられます。

概要

(特筆の無いものは TypeScript/spec.md 「3.2.7 The Undefined Type」での定義によります。

キーワード情報

詳細

「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 | null> = 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」型となる
// (「省略可能」のマークを外した時のみ「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 型」として現れない(推論されない)ため、インデックス値や任意プロパティーを扱うような処理では引き続き注意が必要です。