typeof 型クエリー - TypeScriptキーワード一覧
スポンサーリンク
typeof キーワードのうち、「式が表す型を得る『型クエリー』(type query)で用いるキーワード」としての typeof について説明しています。
概要
型クエリー(type query)は以下のような構文で、何かしらの型を指定する箇所に記述します。
typeof queryExpression
ここでの queryExpression は、(グローバル空間など)その空間で利用できる識別子(変数名など)、またはドット「.
」区切りで記述する参照(プロパティーや名前空間内の変数など)のみとなります。この制限は通常の式における「typeof」演算子とは異なっており、関数呼び出しなどを書くことができないことになります。
この一連の記述を型として指定することにより、queryExpression のデータが取る型を(この記述を行った)該当箇所における型として使用することができます。(具体例などは詳細をご覧ください。)
型クエリーの仕様
構文
(参照: TypeScript/spec.md 「3.8.10 Type Queries」)
TypeQuery:
typeof TypeQueryExpression
TypeQueryExpression:
IdentifierReference
TypeQueryExpression . IdentifierName
- TypeQuery: typeof TypeQueryExpression
- 文法「TypeQuery」は「PrimaryType」の一部として扱われます。具体的には、「string」やオブジェクト型などの名前の付いた型などと同列に扱うことができます。
- キーワード「typeof」に続けて、後述する TypeQueryExpression を記述します。
- TypeQueryExpression: IdentifierReference
- 文法「TypeQueryExpression」に指定できる内容の1つ目は「IdentifierReference」、すなわち参照したい識別子の名前となります。ここではグローバル空間など「.」などを含まない名前を指定します。
- TypeQueryExpression: TypeQueryExpression . IdentifierName
- 文法「TypeQueryExpression」に指定できるもう1つの内容は再帰的な構文であり、「TypeQueryExpression」の後ろにドット「.」と「IdentifierName」を続けます。「IdentifierName」は「IdentifierReference」と似ていますが、予約語も候補に入ります。
※ 通常の式とは異なり、この構文では「[ ]
」を使ったプロパティーアクセスは入りませんが、TypeScript 2.1 以降ではTypeScript PR #11929によって型における「T[K]
」という構文がサポートされており、この「T
」に「typeof ...
」が含まれるため、実質的には型においても「[ ]
」によるプロパティーアクセスが可能になっているように見えます。
※ 「typeof 演算子」では式に括弧「( )」を用いることができますが、型クエリーでは式にあたる箇所(TypeQueryExpression)に括弧を用いることは出来ません。
意味
この構文は式に対する型を得ます。その型は、TypeQueryExpression をあたかも通常の式と同じように解析し、その結果として考えられる型となります。例えば「myVal」という変数が「number 型」であれば、「var p: typeof myVal;
」という文では変数「p」は「number 型」になります。
キーワード情報
- 「typeof」は識別子として予約されています。(ES2015の仕様で「予約語」(キーワード)に含まれ、ユーザー定義の識別子として利用できません。)
- 「識別子として」予約されているだけであるため、プロパティーキー名としては利用可能です。
- 「識別子として」予約されているため、ユーザー定義の型名としても利用できません。(TypeScriptの文法上、
type
およびinterface
などで定義する際に用いる名前(識別子)は BindingIdentifier (ES2015の文法を継承)であり、BindingIdentifier は「予約語」を禁止しているため。refs. TypeScript/spec.md 「A.1 Types」・「A.5 Interfaces」・「A.6 Classes」など, ES2015 - 12.1 Identifiers)
詳細
「typeof」というキーワードは演算子として if 文などの式に利用する他に、型名を指定する箇所で型名の代わりに「typeof」と変数名などを組み合わせて指定するという用法があります。これにより、ある変数と同じ型を用いたい場合にその型をコピペして記述する代わりに「typeof」で参照することができます。これを「型クエリー」と呼びます。
let myValue1: number; // number 型変数
let myValue2: typeof myValue1; // 「myValue1」の型を参照するので number 型の変数となる
型クエリーは、以下のように変数の定義が先行している場合に、それに対する型名を改めて利用したい際に用いることができます。
// TypeScriptの「lib.dom.d.ts」での定義の例(抜粋; バージョン3.0.3時点)
// 型「URL」の定義
interface URL {
// (省略)
}
// グローバル変数「URL」の定義
// 変数「URL」はコンストラクターとして利用できるので型「URL」とは異なる型になる
declare var URL: {
prototype: URL;
new(url: string, base?: string | URL): URL;
createObjectURL(object: any): string;
revokeObjectURL(url: string): void;
};
// (継承関係は省略)
interface Window {
// 「window」オブジェクトでも「URL」をメンバーとして利用できるようにするための定義
// 「window.URL」はまたグローバル変数「URL」と同様コンストラクターとして用いることができるので、
// 型は『型「URL」』ではなく『グローバル変数「URL」と同じ型』を示す「typeof URL」となる
URL: typeof URL;
// (以下略)
}
特に「unique symbol」であるsymbolデータの場合は、その型に対応する型名が定義されないため、明示的にその型を利用したい場合は型クエリーを使って型を得る必要があります。
// ユニークなシンボルの作成
// (「const」なので「unique symbol」と明示しなくても「unique symbol」と扱われる)
const mySymbol: unique symbol = Symbol('mySymbol');
// 「mySymbol」に対応する型を「MySymbolType」としてエイリアス定義
type MySymbolType = typeof mySymbol;
// 「MySymbolType」に対応するデータ(= mySymbol)をメンバーに持つオブジェクト
interface MySymbolTable {
// (「MySymbolType」の箇所はエイリアスを使わない場合「typeof mySymbol」となる)
symHoge: MySymbolType;
}
// 上記オブジェクトのインスタンスを作成
// (明示的に「MySymbolTable」という型を指定しないと「symHoge」が単なる「symbol」型となってしまう)
const mySymbolTable: MySymbolTable = {
// (「symHoge」は「typeof mySymbol」型なので実質「mySymbol」以外を指定することができない)
symHoge: mySymbol
};
※ 上記の例では「MySymbolType」の定義は不要ですが、その場合明示的に型クエリーを使った「mySymbol」の型の参照が必要になります。またこの例においては、(「unique symbol」記述と型エイリアス「MySymbolType」以外は)基本的に型名等の省略は出来ず、特に「const mySymbolTable: MySymbolTable
」の型(名)は省略すると意味が変わってしまうため注意が必要です。
なお、型クエリーにおいては「typeof」の後に指定できる式は限定されており、変数、またはドット「.
」による参照を行っているプロパティー(メンバー)に限られます。
// 「document.body」の型を参照(「document」が「HTMLDocument」であれば「HTMLBodyElement」になる)
let elemBody: typeof document.body;
// ERROR: TS1005: 「(」の前に「,」が必要(※ 関数呼び出しの記述は使えないので「someFunc」で指定が途切れると見なされる)
let result: typeof someFunc();
// 「document.forms[0]」の型を参照しているように見えるが、実際は「document.forms」で指定が途切れており、
// 「document.forms」の型におけるメンバー「0」の型を参照する構文となる
// (「document」が「HTMLDocument」であれば結果的に「HTMLFormElement」になる)
let elemForm: typeof document.forms[0];
※ 関数の戻り値の型を得たい場合は、TypeScript 2.8以降で「ReturnType
」を利用することで得ることができます(例: let result: ReturnType<typeof someFunc>;
)。
※ 3番目の例にある型は(DOMを前提として)「(typeof document.forms)[0]
」または「HTMLCollectionOf<HTMLFormElement>[0]
」と同じであり、「概要」で記載した通りTypeScript 2.1以降で有効な構文です。
ただし、シンボルには通常は参照しやすい型名(またはエイリアス)が付けられていることが多いため、「typeof」による型クエリーを用いるケースはあまりないと思われます。前述の変数定義が先行するケースや、変数名やプロパティー名等で一見して型名が分からないような場合には用いることがあると思われます。