|
)&
)keyof
T[K]
typeof
この章では、TypeScriptでコンパイル時に型を使って計算する方法を探ります。
この章では、型を使って計算する方法を学ぶことに重点を置いています。そのため、リテラル型を多用し、例は実用性は低いです。
TypeScriptコードの次の2つのレベルを考えてみましょう。
型レベルは、プログラムレベルのメタレベルです。
レベル | 利用可能時期 | オペランド | 操作 |
---|---|---|---|
プログラムレベル | 実行時 | 値 | 関数 |
型レベル | コンパイル時 | 特定の型 | ジェネリック型 |
型で計算できるというのはどういう意味でしょうか?次のコードは例です。
type ObjectLiteralType = {
: 1,
first: 2,
second;
}
// %inferred-type: "first" | "second"
type Result = keyof ObjectLiteralType; // (A)
A行では、以下の手順を実行しています。
ObjectLiteralType
型です。keyof
演算を適用します。これは、オブジェクト型のプロパティキーを一覧表示します。keyof
の出力をResult
という名前で指定します。型レベルでは、以下の「値」で計算できます。
type ObjectLiteralType = {
: string,
prop1: number,
prop2;
}
interface InterfaceType {: string;
prop1: number;
prop2
}
type TupleType = [boolean, bigint];
//::::: Nullish types and literal types :::::
// Same syntax as values, but they are all types!
type UndefinedType = undefined;
type NullType = null;
type BooleanLiteralType = true;
type NumberLiteralType = 12.34;
type BigIntLiteralType = 1234n;
type StringLiteralType = 'abc';
ジェネリック型は、メタレベルでの関数です。例えば、
type Wrap<T> = [T];
ジェネリック型Wrap<>
にはパラメータT
があります。その結果は、タプル型でラップされたT
です。このメタ関数を使用する方法は次のとおりです。
// %inferred-type: [string]
type Wrapped = Wrap<string>;
パラメータstring
をWrap<>
に渡し、結果にエイリアスWrapped
を付けます。結果は、単一のコンポーネント(型string
)を持つタプル型です。
|
)型演算子|
は、ユニオン型を作成するために使用されます。
type A = 'a' | 'b' | 'c';
type B = 'b' | 'c' | 'd';
// %inferred-type: "a" | "b" | "c" | "d"
type Union = A | B;
型A
と型`B`を集合とみなすと、`A | B`はこれらの集合の集合論的和集合です。言い換えれば、結果のメンバーは、少なくとも1つのオペランドのメンバーです。
構文的には、ユニオン型の最初のコンポーネントの前に`|`を置くこともできます。これは、型の定義が複数行にまたがる場合に便利です。
type A =
| 'a'
| 'b'
| 'c'
;
TypeScriptは、メタ値の集合をリテラル型のユニオンとして表します。すでにその例を見てきました。
type Obj = {
: 1,
first: 2,
second;
}
// %inferred-type: "first" | "second"
type Result = keyof Obj;
すぐに、そのようなコレクションをループするための型レベルの操作が出てきます。
ユニオン型の各メンバーは、少なくとも1つのコンポーネント型のメンバーであるため、すべてのコンポーネント型で共有されているプロパティにのみ安全にアクセスできます(A行)。他のプロパティにアクセスするには、型ガードが必要です(B行)。
type ObjectTypeA = {
: bigint,
propA: string,
sharedProp
}type ObjectTypeB = {
: boolean,
propB: string,
sharedProp
}
type Union = ObjectTypeA | ObjectTypeB;
function func(arg: Union) {
// string
.sharedProp; // (A) OK
arg// @ts-expect-error: Property 'propB' does not exist on type 'Union'.
.propB; // error
arg
if ('propB' in arg) { // (B) type guard
// ObjectTypeB
;
arg
// boolean
.propB;
arg
} }
&
)型演算子&
は、インターセクション型を作成するために使用されます。
type A = 'a' | 'b' | 'c';
type B = 'b' | 'c' | 'd';
// %inferred-type: "b" | "c"
type Intersection = A & B;
型`A`と型`B`を集合とみなすと、`A & B`はこれらの集合の集合論的共通部分です。言い換えれば、結果のメンバーは両方のオペランドのメンバーです。
2つのオブジェクト型のインターセクションは、両方の型のプロパティを持ちます。
type Obj1 = { prop1: boolean };
type Obj2 = { prop2: number };
type Both = {
: boolean,
prop1: number,
prop2;
}
// Type Obj1 & Obj2 is assignable to type Both
// %inferred-type: true
type IntersectionHasBothProperties = IsAssignableTo<Obj1 & Obj2, Both>;
(ジェネリック型IsAssignableTo<>
については後述します。)
オブジェクト型`Named`を別の型`Obj`にミックスインする場合、インターセクション型が必要です(A行)。
interface Named {: string;
name
}function addName<Obj extends object>(obj: Obj, name: string)
: Obj & Named // (A)
{= obj as (Obj & Named);
const namedObj .name = name;
namedObj;
return namedObj
}
= {
const obj : 'Doe',
last;
}
// %inferred-type: { last: string; } & Named
= addName(obj, 'Jane'); const namedObj
条件付き型は、次の構文を持ちます。
? «ThenType» : «ElseType» «Type2» extends «Type1»
Type2
が`Type1`に代入可能な場合、この型式の結果は`ThenType`です。そうでない場合は、`ElseType`です。
次の例では、`Wrap<>`は、値が数値であるプロパティ`.length`を持っている場合にのみ、型を1要素のタプルにラップします。
type Wrap<T> = T extends { length: number } ? [T] : T;
// %inferred-type: [string]
type A = Wrap<string>;
// %inferred-type: RegExp
type B = Wrap<RegExp>;
条件付き型を使用して、代入可能性チェックを実装できます。
type IsAssignableTo<A, B> = A extends B ? true : false;
// Type `123` is assignable to type `number`
// %inferred-type: true
type Result1 = IsAssignableTo<123, number>;
// Type `number` is not assignable to type `123`
// %inferred-type: false
type Result2 = IsAssignableTo<number, 123>;
型の関係「代入可能性」の詳細については、[コンテンツは含まれていません]を参照してください。
条件付き型は分配的です。条件付き型`C`をユニオン型`U`に適用することは、`C`を`U`の各コンポーネントに適用したユニオンと同じです。これは例です。
type Wrap<T> = T extends { length: number } ? [T] : T;
// %inferred-type: boolean | [string] | [number[]]
type C1 = Wrap<boolean | string | number[]>;
// Equivalent:
type C2 = Wrap<boolean> | Wrap<string> | Wrap<number[]>;
言い換えれば、分配性により、ユニオン型のコンポーネントを「ループ」することができます。
これは、分配性の別の例です。
type AlwaysWrap<T> = T extends any ? [T] : [T];
// %inferred-type: ["a"] | ["d"] | [{ a: 1; } & { b: 2; }]
type Result = AlwaysWrap<'a' | ({ a: 1 } & { b: 2 }) | 'd'>;
集合として解釈すると、型`never`は空です。したがって、ユニオン型に現れても無視されます。
// %inferred-type: "a" | "b"
type Result = 'a' | 'b' | never;
つまり、`never`を使用してユニオン型のコンポーネントを無視できます。
type DropNumbers<T> = T extends number ? never : T;
// %inferred-type: "a" | "b"
type Result1 = DropNumbers<1 | 'a' | 2 | 'b'>;
thenブランチとelseブランチの型式を入れ替えると、こうなります。
type KeepNumbers<T> = T extends number ? T : never;
// %inferred-type: 1 | 2
type Result2 = KeepNumbers<1 | 'a' | 2 | 'b'>;
Exclude<T, U>
ユニオンから型を除外することは非常に一般的な操作であるため、TypeScriptは組み込みユーティリティ型`Exclude<T, U>`を提供しています。
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T;
// %inferred-type: "a" | "b"
type Result1 = Exclude<1 | 'a' | 2 | 'b', number>;
// %inferred-type: "a" | 2
type Result2 = Exclude<1 | 'a' | 2 | 'b', 1 | 'b' | 'c'>;
Extract<T, U>
`Exclude<T, U>`の逆は、`Extract<T, U>`です。
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never;
// %inferred-type: 1 | 2
type Result1 = Extract<1 | 'a' | 2 | 'b', number>;
// %inferred-type: 1 | "b"
type Result2 = Extract<1 | 'a' | 2 | 'b', 1 | 'b' | 'c'>;
JavaScriptの三項演算子と同様に、TypeScriptの条件付き型演算子も連結できます。
type LiteralTypeName<T> =
undefined ? "undefined" :
T extends null ? "null" :
T extends boolean ? "boolean" :
T extends number ? "number" :
T extends bigint ? "bigint" :
T extends string ? "string" :
T extends never;
// %inferred-type: "bigint"
type Result1 = LiteralTypeName<123n>;
// %inferred-type: "string" | "number" | "boolean"
type Result2 = LiteralTypeName<true | 1 | 'a'>;
infer
と条件付き型https://typescript.dokyumento.jp/docs/handbook/advanced-types.html#type-inference-in-conditional-types
例
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
例
type Syncify<Interf> = {
keyof Interf]:
[K in extends (...args: any[]) => Promise<infer Result>
Interf[K] ? (...args: Parameters<Interf[K]>) => Result
: Interf[K];
;
}
// Example:
interface AsyncInterface {compute(arg: number): Promise<boolean>;
createString(): Promise<String>;
}
type SyncInterface = Syncify<AsyncInterface>;
// type SyncInterface = {
// compute: (arg: number) => boolean;
// createString: () => String;
// }
マップ型は、キーのコレクションをループすることによってオブジェクトを生成します。例えば、
// %inferred-type: { a: number; b: number; c: number; }
type Result = {
'a' | 'b' | 'c']: number
[K in ; }
演算子`in`は、マップ型の重要な部分です。新しいオブジェクトリテラル型のキーがどこから来るかを指定します。
Pick<T, K>
次の組み込みユーティリティ型を使用すると、既存のオブジェクト型のどのプロパティを保持するかを指定することで、新しいオブジェクトを作成できます。
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
: T[P];
[P in K]; }
次のように使用されます。
type ObjectLiteralType = {
: 1,
eeny: 2,
meeny: 3,
miny: 4,
moe;
}
// %inferred-type: { eeny: 1; miny: 3; }
type Result = Pick<ObjectLiteralType, 'eeny' | 'miny'>;
Omit<T, K>
次の組み込みユーティリティ型を使用すると、既存のオブジェクト型のどのプロパティを省略するかを指定することで、新しいオブジェクト型を作成できます。
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
説明
`K extends keyof any`は、`K`がすべてのプロパティキーの型のサブタイプでなければならないことを意味します。
// %inferred-type: string | number | symbol
type Result = keyof any;
`Exclude<keyof T, K>`は、`T`のキーを取り、`K`に記載されているすべての「値」を削除することを意味します。
`Omit<>`は次のように使用されます。
type ObjectLiteralType = {
: 1,
eeny: 2,
meeny: 3,
miny: 4,
moe;
}
// %inferred-type: { meeny: 2; moe: 4; }
type Result = Omit<ObjectLiteralType, 'eeny' | 'miny'>;
keyof
型演算子`keyof`にはすでに遭遇しました。これは、オブジェクト型のプロパティキーを一覧表示します。
type Obj = {
0: 'a',
1: 'b',
: 'c',
prop0: 'd',
prop1;
}
// %inferred-type: 0 | 1 | "prop0" | "prop1"
type Result = keyof Obj;
`keyof`をタプル型に適用すると、やや予期しない結果になる場合があります。
// number | "0" | "1" | "2" | "length" | "pop" | "push" | ···
type Result = keyof ['a', 'b', 'c'];
結果には以下が含まれます。
"0" | "1" | "2"
"pop" | "push" | ···
空のオブジェクトリテラル型のプロパティキーは、空集合`never`です。
// %inferred-type: never
type Result = keyof {};
`keyof`がインターセクション型とユニオン型をどのように処理するかは次のとおりです。
type A = { a: number, shared: string };
type B = { b: number, shared: string };
// %inferred-type: "a" | "b" | "shared"
type Result1 = keyof (A & B);
// %inferred-type: "shared"
type Result2 = keyof (A | B);
`A & B`が型`A`と型`B`の両方のプロパティを持っていることを覚えていれば、これは理にかなっています。`A`と`B`は共通のプロパティ`.shared`しか持っておらず、これが`Result2`を説明しています。
T[K]
インデックスアクセス演算子`T[K]`は、キーが型`K`に代入可能な`T`のすべてのプロパティの型を返します。`T[K]`は、ルックアップ型とも呼ばれます。
演算子が使用されている例を次に示します。
type Obj = {
0: 'a',
1: 'b',
: 'c',
prop0: 'd',
prop1;
}
// %inferred-type: "a" | "b"
type Result1 = Obj[0 | 1];
// %inferred-type: "c" | "d"
type Result2 = Obj['prop0' | 'prop1'];
// %inferred-type: "a" | "b" | "c" | "d"
type Result3 = Obj[keyof Obj];
角かっこ内の型は、すべてのプロパティキーの型(`keyof`によって計算される)に代入可能でなければなりません。そのため、`Obj[number]`と`Obj[string]`は許可されていません。ただし、インデックス付き型にインデックスシグネチャがある場合、`number`と`string`をインデックスタイプとして使用できます(A行)。
type Obj = {
: string]: RegExp, // (A)
[key;
}
// %inferred-type: string | number
type KeysOfObj = keyof Obj;
// %inferred-type: RegExp
type ValuesOfObj = Obj[string];
`KeysOfObj`には型`number`が含まれています。これは、JavaScript(したがってTypeScript)では数値キーが文字列キーのサブセットであるためです。
タプル型もインデックスアクセスをサポートしています。
type Tuple = ['a', 'b', 'c', 'd'];
// %inferred-type: "a" | "b"
type Elements = Tuple[0 | 1];
ブラケット演算子も分配的です。
type MyType = { prop: 1 } | { prop: 2 } | { prop: 3 };
// %inferred-type: 1 | 2 | 3
type Result1 = MyType['prop'];
// Equivalent:
type Result2 =
| { prop: 1 }['prop']
| { prop: 2 }['prop']
| { prop: 3 }['prop']
;
typeof
型演算子`typeof`は、(JavaScriptの)値をその(TypeScriptの)型に変換します。そのオペランドは、識別子またはドットで区切られた識別子のシーケンスでなければなりません。
= 'abc';
const str
// %inferred-type: "abc"
type Result = typeof str;
最初の`'abc'`は値であり、2番目の`"abc"`はその型である文字列リテラル型です。
`typeof`を使用する別の例を次に示します。
= (x: number) => x + x;
const func // %inferred-type: (x: number) => number
type Result = typeof func;
§14.1.2「型にシンボルを追加する」では、`typeof`の興味深いユースケースについて説明しています。