Menu

TypeScriptのtypeof: 型ガードと型としての typeof

TypeScriptの typeof には2つの役割があります。コードの中では実行時に "string"、"number"、"object" などを返すJavaScriptの演算子で、TypeScriptはこれで型を絞り込みます。型の中では変数の型をコピーする型クエリで、keyof typeof obj や ReturnType<typeof fn> のように使います。

このページのコードはエディタで実行できます - 編集してすぐに結果を確認できます。

TypeScriptの typeof には2つの意味があります。式の中ではJavaScriptの演算子で、実行されて "string" や "number" のような文字列を返し、その文字列を比較するとTypeScriptが変数を絞り込みます。型の位置では型クエリで、typeof config は変数 config の静的な型を返します。

最後の行が違いを示しています。console.log の中の typeof custom は実行時の演算子なので object を出力しますが、type Options = のあとの typeof defaults はコンパイル時のクエリで、JavaScriptには残りません。

2つの意味の比較

実行時の typeof(JavaScript)型クエリの typeof(TypeScript)
書く場所あらゆる式: if、return、console.log(...)型の位置: : のあと、type X = ... の中、<...> の中
例typeof x === "string"let y: typeof x;
評価されるときコードの実行時コンパイラーが評価し、その後消える
結果8種類の文字列のどれか型
用途値をチェックして型を絞り込む値から型を作る

TypeScriptは書かれた位置で2つを区別するので、同じキーワードが同時に両方の意味になることはありません。

実行時の typeof の結果

実行時の演算子は8種類の文字列のどれかを返します。よく使う値で何が返るか、実行して確かめてください:

値typeof の結果備考
"hi"、テンプレート文字列"string"
42、3.14、NaN、Infinity"number"NaN は数値。Number.isNaN でチェックする
10n"bigint"
true、false"boolean"
undefined"undefined"
Symbol("id")"symbol"
関数、アロー関数、クラス"function"クラスはコンストラクター関数
null"object"JavaScriptの歴史的なバグ
{}、[]、new Date()、new Map()"object"配列もオブジェクト。Array.isArray を使う
new String("x")、new Number(1)"object"ラッパーオブジェクト。使わない

素のJavaScriptでは、宣言されていない変数に typeof を使うと、例外を投げずに "undefined" を返します。TypeScriptではそのコードはコンパイルできません: Cannot find name 'notDeclared'.(TS2304)。

typeof による絞り込み

if、switch、三項演算子、&& のあとで typeof を比較すると、各ブランチで変数が絞り込まれます。プリミティブのユニオン型を扱う標準的な方法で、unknown に対して最初に使うチェックです。

typeof x !== "string" は逆方向に、string 以外のすべてに絞り込みます。また、コンパイラーは8種類の文字列を知っているので、比較の文字列を打ち間違えると、黙って常に false になるチェックではなくコンパイルエラーになります:

コンパイラーは index.ts(3,7): error TS2367: This comparison appears to be unintentional because the types '"bigint" | "boolean" | "function" | "number" | "object" | "string" | "symbol" | "undefined"' and '"strnig"' have no overlap. を出力します。素のJavaScriptなら、この打ち間違いで文字列はすべて黙って number の経路に進んでしまいます。つづりを直すと text が出力されます。

typeof null の落とし穴

typeof null は "object" なので、TypeScriptは typeof x === "object" を object ではなく object | null に絞り込みます。null のチェックを追加しないと、プロパティを読むのはエラーです(TS18047 'x' is possibly 'null'.)。

typeof では配列、日付、Map、普通のオブジェクトを区別できません。どれも "object" です。配列には Array.isArray(x)、クラスのインスタンスには x instanceof Date を使います。インターフェースは消去されるので、値がインターフェースに一致するかを実行時にチェックする方法は typeof にもほかの何にもありません。その場合はプロパティをチェックする型ガード関数を書きます。

型の位置の typeof

型の中の typeof someVariable は、その変数についてTypeScriptが推論した(または宣言された)型をコピーします。すでにある値の型を手で書く手間が省け、値と型がずれることもありません。

const theme = {
  primary: "#3178c6",
  spacing: 8,
  dark: false,
};

type Theme = typeof theme;
// { primary: string; spacing: number; dark: boolean }

function withSpacing(t: Theme, factor: number): Theme {
  return { ...t, spacing: t.spacing * factor };
}

console.log(withSpacing(theme, 2)); // { primary: '#3178c6', spacing: 16, dark: false }

let userName = "Ada";
const fixed = "Ada";
type A = typeof userName; // string
type B = typeof fixed;    // "Ada" (a const keeps its literal type)

結果は変数の宣言のしかたによって変わります。let やオブジェクトのプロパティは広げられ(string、number)、const のプリミティブはリテラル型のままです。オブジェクトや配列に as const を付けると、typeof は奥まですべて readonly のリテラル型になります。typeof はプロパティアクセスも受け付けます(typeof theme.spacing は number)が、任意の式は受け付けません。type T = typeof getTheme(); は構文エラー(';' expected.、TS1005)です。呼び出しが返す型には ReturnType<typeof getTheme> を使います。

keyof typeof: オブジェクトのキーを型にする

keyof typeof obj はいちばんよく使う組み合わせです。typeof がオブジェクトを型に変え、keyof がそのキーを取り出すので、オブジェクトが変わっても正しいままのプロパティ名のユニオン型が得られます。

定数の配列に同じ方法を使うと、値のユニオン型が得られます。const roles = ["admin", "editor"] as const なら、型 (typeof roles)[number] は "admin" | "editor" です。as const がなければただの string になります。keyof 単体については keyof のページで説明しています。

ReturnType<typeof fn> とクラスの typeof

ReturnType や Parameters のようなユーティリティ型は関数の型を受け取ります。関数名は値なので、先に typeof を通します:

ReturnType<createUser> と書くと 'createUser' refers to a value, but is being used as a type here. Did you mean 'typeof createUser'?(TS2749)で失敗します。コンパイラーがまさにこの書き方を求めているわけです。

名前が値でもあり型でもある唯一のケースがクラスです。型としての User はクラスのインスタンスを意味し、typeof User はクラスそのもの、つまり static メンバーを含むコンストラクターを意味します:

class Point {
  static origin = new Point(0, 0);
  constructor(public x: number, public y: number) {}
}

const p: Point = new Point(1, 2);        // an instance
const Ctor: typeof Point = Point;        // the class (constructor + statics)
const q = new Ctor(3, 4);                // q: Point
type Instance = InstanceType<typeof Point>; // Point

typeof、instanceof、型ガードの比較

チェック対象例
typeofプリミティブ、関数、「オブジェクトかどうか」typeof x === "number"
Array.isArray配列Array.isArray(x)
instanceofクラスのインスタンス(Date、Error、自作のクラス)x instanceof Date
inプロパティによるオブジェクトのユニオン型"email" in x
型ガード関数インターフェース、型エイリアス、その他すべてx is User を返す isUser(x)

条件に合う、いちばん限定的な方法を選びましょう。クラスのインスタンスについては instanceof を参照してください。インターフェースで表した形については、使える実行時のチェックは型ガード関数だけです。

よくある質問

TypeScriptの typeof は何をしますか?

書く場所によって2つの違うことをします。式の中の typeof x はJavaScriptの演算子で、実行されて "string" や "object" のような文字列を返し、その文字列を比較するとTypeScriptが x を絞り込みます。型注釈の中の typeof x は型クエリで、コンパイラーが評価して変数 x の静的な型を返し、出力されるJavaScriptからは消えます。

TypeScriptの keyof typeof とは何ですか?

keyof typeof obj は、オブジェクトのプロパティ名を文字列リテラル型のユニオンとして返します。typeof obj がオブジェクトの値を型に変え、keyof がその型のキーを取り出します。const colors = { red: "#f00", blue: "#00f" } なら、keyof typeof colors は "red" | "blue" です。

typeof null が "object" を返すのはなぜですか?

JavaScriptの最初のバージョンからあるバグで、Webを壊さずに直すことはもうできません。TypeScriptはこれを再現しています。typeof x === "object" のあとでは x は object | null に絞り込まれるので、プロパティを読む前に x !== null もチェックする必要があります。

TypeScriptで値が配列かどうかをチェックするには?

Array.isArray(value) を使います。これで配列型に絞り込まれます。typeof ではできません。typeof [] は、普通のオブジェクトや null と同じく "object" です。

TypeScriptの typeof と instanceof の違いは何ですか?

typeof は値のプリミティブの種類("string"、"number"、"function"、"object" など)をチェックします。instanceof は、オブジェクトが Date や自分で作った class User のような特定のクラスやコンストラクターから作られたかをチェックします。プリミティブには typeof、クラスのインスタンスには instanceof を使います。どちらも実行時に存在しないインターフェースや型エイリアスはチェックできません。

Coddy programming languages illustration

Coddyでコードを学ぼう

始める