if
とタイプガードによる絞り込みswitch
とタイプガードによる絞り込みunknown
型===
)typeof
、instanceof
、Array.isArray
in
を使用した個別のプロパティのチェックasserts «cond»
asserts «arg» is «type»
@hqoss/guards
: タイプガードを持つライブラリTypeScriptでは、値の型が一部の操作に対して汎用すぎる場合があります。たとえば、ユニオン型です。この章では、次の質問に答えます。
T
を T
のサブセットに変更することを意味します。たとえば、型 null|string
を型 string
に絞り込むと便利なことがよくあります。typeof
と instanceof
はタイプガードです。静的型がどのように汎用すぎるかを確認するには、次の関数 getScore()
を考えてみましょう。
.equal(
assertgetScore('*****'), 5);
.equal(
assertgetScore(3), 3);
getScore()
のスケルトンは次のようになります。
function getScore(value: number|string): number {
// ···
}
getScore()
の本体内では、value
の型が number
なのか string
なのかわかりません。知る前に、value
を実際に操作することはできません。
if
とタイプガードによる絞り込み解決策は、実行時に typeof
を使用して value
の型を確認することです (行 A と行 B)。
function getScore(value: number|string): number {
if (typeof value === 'number') { // (A)
// %inferred-type: number
;
value;
return value
}if (typeof value === 'string') { // (B)
// %inferred-type: string
;
value.length;
return value
}new Error('Unsupported value: ' + value);
throw }
この章では、型を値の集合として解釈します。(この解釈と別の解釈の詳細については、[コンテンツは含まれていません] を参照してください。)
行 A と行 B から始まる then ブロック内では、実行したチェックにより、value
の静的型が変更されます。元の型 number|string
のサブセットを操作しています。このように型のサイズを縮小することを *絞り込み* と呼びます。 typeof
や同様の実行時操作の結果を確認することを *タイプガード* と呼びます。
絞り込みは value
の元の型を変更するのではなく、チェックを通過するにつれてより具体的な型にするだけであることに注意してください。
switch
とタイプガードによる絞り込みif
の代わりに switch
を使用した場合でも、絞り込みは機能します。
function getScore(value: number|string): number {
switch (typeof value) {
'number':
case // %inferred-type: number
;
value;
return value'string':
case // %inferred-type: string
;
value.length;
return valuedefault:
new Error('Unsupported value: ' + value);
throw
} }
型が汎用すぎる場合のその他の例を次に示します。
Null許容型
function func1(arg: null|string) {}
function func2(arg: undefined|string) {}
判別可能なユニオン
type Teacher = { kind: 'Teacher', teacherId: string };
type Student = { kind: 'Student', studentId: string };
type Attendee = Teacher | Student;
function func3(attendee: Attendee) {}
オプションパラメータの型
function func4(arg?: string) {
// %inferred-type: string | undefined
;
arg }
これらの型はすべてユニオン型であることに注意してください。
unknown
型値が unknown
型 の場合、ほとんど何もできず、最初に型を絞り込む必要があります (行 A)。
function parseStringLiteral(stringLiteral: string): string {
: unknown = JSON.parse(stringLiteral);
const resultif (typeof result === 'string') { // (A)
;
return result
}new Error('Not a string literal: ' + stringLiteral);
throw }
言い換えれば、unknown
型は汎用すぎるため、絞り込む必要があります。ある意味で、unknown
もユニオン型です (すべての型のユニオン)。
見てきたように、*タイプガード* は、オペランドが実行時に特定の条件を満たしているかどうかによって、true
または false
を返す操作です。TypeScript の型推論は、結果が true
の場合にオペランドの静的型を絞り込むことで、タイプガードをサポートします。
===
)厳密等価はタイプガードとして機能します。
function func(value: unknown) {
if (value === 'abc') {
// %inferred-type: "abc"
;
value
} }
一部のユニオン型では、===
を使用してコンポーネントを区別できます。
interface Book {: null | string;
title: string;
isbn
}
function getTitle(book: Book) {
if (book.title === null) {
// %inferred-type: null
.title;
book'(Untitled)';
return
} else {// %inferred-type: string
.title;
book.title;
return book
} }
ユニオン型コンポーネントを含めるために ===
を使用し、除外するために !===
を使用することは、そのコンポーネントが *シングルトン型* (メンバーが 1 つだけの集合) である場合にのみ機能します。 null
型はシングルトン型です。唯一のメンバーは値 null
です。
typeof
、instanceof
、Array.isArray
これらは 3 つの一般的な組み込みタイプガードです。
function func(value: Function|Date|number[]) {
if (typeof value === 'function') {
// %inferred-type: Function
;
value
}
if (value instanceof Date) {
// %inferred-type: Date
;
value
}
if (Array.isArray(value)) {
// %inferred-type: number[]
;
value
} }
then ブロック内で value
の静的型がどのように絞り込まれるかに注意してください。
in
を使用した個別のプロパティのチェック個別のプロパティをチェックするために使用される場合、演算子 in
はタイプガードです。
type FirstOrSecond =
| {first: string}
| {second: string};
function func(firstOrSecond: FirstOrSecond) {
if ('second' in firstOrSecond) {
// %inferred-type: { second: string; }
;
firstOrSecond
} }
次のチェックは機能しないことに注意してください。
function func(firstOrSecond: FirstOrSecond) {
// @ts-expect-error: Property 'second' does not exist on
// type 'FirstOrSecond'. [...]
if (firstOrSecond.second !== undefined) {
// ···
} }
この場合の問題は、絞り込みを行わずに、型が FirstOrSecond
である値のプロパティ .second
にアクセスできないことです。
in
は非ユニオン型を絞り込みません残念ながら、in
はユニオン型にのみ役立ちます。
function func(obj: object) {
if ('name' in obj) {
// %inferred-type: object
;
obj
// @ts-expect-error: Property 'name' does not exist on type 'object'.
.name;
obj
} }
判別可能なユニオンでは、ユニオン型のコンポーネントには、コンポーネントごとに値が異なる 1 つ以上の共通プロパティがあります。このようなプロパティは *判別子* と呼ばれます。
判別子の値のチェックはタイプガードです。
type Teacher = { kind: 'Teacher', teacherId: string };
type Student = { kind: 'Student', studentId: string };
type Attendee = Teacher | Student;
function getId(attendee: Attendee) {
switch (attendee.kind) {
'Teacher':
case // %inferred-type: { kind: "Teacher"; teacherId: string; }
;
attendee.teacherId;
return attendee'Student':
case // %inferred-type: { kind: "Student"; studentId: string; }
;
attendee.studentId;
return attendeedefault:
new Error();
throw
} }
前の例では、.kind
は判別子です。ユニオン型 Attendee
の各コンポーネントには、このプロパティがあり、一意の値が設定されています。
if
文と等価チェックは、switch
文と同様に機能します。
function getId(attendee: Attendee) {
if (attendee.kind === 'Teacher') {
// %inferred-type: { kind: "Teacher"; teacherId: string; }
;
attendee.teacherId;
return attendeeif (attendee.kind === 'Student') {
} else // %inferred-type: { kind: "Student"; studentId: string; }
;
attendee.studentId;
return attendee
} else {new Error();
throw
} }
プロパティの型 (プロパティ名のチェーンを介してアクセスするネストされたプロパティの型も含む) を絞り込むこともできます。
type MyType = {
?: number | string,
prop;
}function func(arg: MyType) {
if (typeof arg.prop === 'string') {
// %inferred-type: string
.prop; // (A)
arg
.forEach((x) => {
[]// %inferred-type: string | number | undefined
.prop; // (B)
arg;
})
// %inferred-type: string
.prop;
arg
= {};
arg
// %inferred-type: string | number | undefined
.prop; // (C)
arg
} }
前のコードのいくつかの場所を見てみましょう。
arg.prop
の型を絞り込みました。.every()
は絞り込みません.every()
を使用してすべての配列要素が null 以外であることを確認した場合、TypeScript は mixedValues
の型を絞り込みません (行 A)。
: ReadonlyArray<undefined|null|number> =
const mixedValues1, undefined, 2, null];
[
if (mixedValues.every(isNotNullish)) {
// %inferred-type: readonly (number | null | undefined)[]
; // (A)
mixedValues }
mixedValues
は読み取り専用である必要があることに注意してください。そうでない場合、それへの別の参照によって、if
文内で null
を mixedValues
にプッシュすることが静的に許可されます。しかし、それは mixedValues
の絞り込まれた型を正しくないものにします。
前のコードでは、次の *ユーザー定義タイプガード* を使用しています (それが何であるかについては、後ほど詳しく説明します)。
function isNotNullish<T>(value: T): value is NonNullable<T> { // (A)
!== undefined && value !== null;
return value }
NonNullable<Union>
(行 A) は、ユーティリティ型 であり、ユニオン型 Union
から undefined
型と null
型を削除します。
.filter()
は、より狭い型の配列を生成します.filter()
は、より狭い型の配列を生成します (つまり、既存の型を実際に絞り込むわけではありません)。
// %inferred-type: (number | null | undefined)[]
= [1, undefined, 2, null];
const mixedValues
// %inferred-type: number[]
= mixedValues.filter(isNotNullish);
const numbers
function isNotNullish<T>(value: T): value is NonNullable<T> { // (A)
!== undefined && value !== null;
return value }
残念ながら、タイプガード関数を直接使用する必要があります。タイプガードを持つアロー関数は十分ではありません。
// %inferred-type: (number | null | undefined)[]
= mixedValues.filter(
const stillMixed1 => x !== undefined && x !== null);
x
// %inferred-type: (number | null | undefined)[]
= mixedValues.filter(
const stillMixed2 => typeof x === 'number'); x
TypeScript では、独自のタイプガードを定義できます。たとえば、次のようになります。
function isFunction(value: unknown): value is Function {
return typeof value === 'function';
}
戻り値の型 value is Function
は *型述語* です。これは isFunction()
の型シグネチャの一部です。
// %inferred-type: (value: unknown) => value is Function
; isFunction
ユーザー定義タイプガードは常にブール値を返す必要があります。 isFunction(x)
が true
を返すと、TypeScript は実引数 x
の型を Function
に絞り込みます。
function func(arg: unknown) {
if (isFunction(arg)) {
// %inferred-type: Function
; // type is narrowed
arg
} }
TypeScript は、ユーザー定義タイプガードの結果をどのように計算するかは気にしないことに注意してください。これにより、使用するチェックに関して大きな自由度が得られます。たとえば、isFunction()
は次のように実装することもできます。
function isFunction(value: any): value is Function {
try {value(); // (A)
;
return true
} catch {;
return false
} }
残念ながら、unknown
型では行 A の関数呼び出しを実行できないため、パラメータ value
に any
型を使用する必要があります。
isArrayWithInstancesOf()
/**
* This type guard for Arrays works similarly to `Array.isArray()`,
* but also checks if all Array elements are instances of `T`.
* As a consequence, the type of `arr` is narrowed to `Array<T>`
* if this function returns `true`.
*
* Warning: This type guard can make code unsafe – for example:
* We could use another reference to `arr` to add an element whose
* type is not `T`. Then `arr` doesn’t have the type `Array<T>`
* anymore.
*/
function isArrayWithInstancesOf<T>(
: any, Class: new (...args: any[])=>T)
arr: arr is Array<T>
{if (!Array.isArray(arr)) {
;
return false
}if (!arr.every(elem => elem instanceof Class)) {
;
return false
}
// %inferred-type: any[]
; // (A)
arr
;
return true }
行 A では、arr
の推論された型が Array<T>
*ではない* ことがわかりますが、チェックにより、現在はそうなっています。そのため、true
を返すことができます。TypeScript は私たちを信頼し、isArrayWithInstancesOf()
を使用すると Array<T>
に絞り込みます。
: unknown = {};
const valueif (isArrayWithInstancesOf(value, RegExp)) {
// %inferred-type: RegExp[]
;
value }
isTypeof()
これは、TypeScript で typeof
を実装するための最初の試みです。
/**
* An implementation of the `typeof` operator.
*/
function isTypeof<T>(value: unknown, prim: T): value is T {
if (prim === null) {
=== null;
return value
}!== null && (typeof prim) === (typeof value);
return value }
理想的には、文字列 (つまり、typeof
の結果の 1 つ) を介して value
の予期される型を指定できるはずです。しかし、その場合、その文字列から型 T
を派生させる必要があり、その方法がすぐにはわかりません (後ほど説明するように、方法があります)。回避策として、T
のメンバー prim
を介して T
を指定します。
: unknown = {};
const valueif (isTypeof(value, 123)) {
// %inferred-type: number
;
value }
より良い解決策は、オーバーロードを使用することです (いくつかのケースは省略されています)。
/**
* A partial implementation of the `typeof` operator.
*/
function isTypeof(value: any, typeString: 'boolean'): value is boolean;
function isTypeof(value: any, typeString: 'number'): value is number;
function isTypeof(value: any, typeString: 'string'): value is string;
function isTypeof(value: any, typeString: string): boolean {
=== typeString;
return typeof value
}
: unknown = {};
const valueif (isTypeof(value, 'boolean')) {
// %inferred-type: boolean
;
value }
(このアプローチは、Nick Fisher によるアイデアです。)
別の方法として、インターフェースを文字列から型へのマップとして使用する方法があります (いくつかのケースは省略されています)。
interface TypeMap {: boolean;
boolean: number;
number: string;
string
}
/**
* A partial implementation of the `typeof` operator.
*/
function isTypeof<T extends keyof TypeMap>(value: any, typeString: T)
: value is TypeMap[T] {
=== typeString;
return typeof value
}
: unknown = {};
const valueif (isTypeof(value, 'string')) {
// %inferred-type: string
;
value }
(このアプローチは、Ran Lottem によるアイデアです。)
アサーション関数は、パラメータが特定の基準を満たしているかどうかを確認し、満たしていない場合は例外をスローします。たとえば、多くの言語でサポートされているアサーション関数の1つは、assert()
です。 assert(cond)
は、ブール条件cond
がfalse
の場合に例外をスローします。
Node.jsでは、assert()
は組み込みモジュールassert
を介してサポートされています。次のコードは、A行でそれを使用しています
import assert from 'assert';
function removeFilenameExtension(filename: string) {
= filename.lastIndexOf('.');
const dotIndex assert(dotIndex >= 0); // (A)
.slice(0, dotIndex);
return filename }
TypeScriptの型推論は、アサーション関数を_アサーションシグネチャ_を戻り値の型としてマークすると、特別なサポートを提供します。関数の戻り値とその方法に関して、アサーションシグネチャはvoid
と同等です。ただし、さらに絞り込みをトリガーします。
アサーションシグネチャには2種類あります
asserts «cond»
asserts «arg» is «type»
asserts «cond»
次の例では、アサーションシグネチャasserts condition
は、パラメータcondition
がtrue
でなければならないことを示しています。そうでない場合は、例外がスローされます。
function assertTrue(condition: boolean): asserts condition {
if (!condition) {
new Error();
throw
} }
assertTrue()
は、このように絞り込みを行います
function func(value: unknown) {
assertTrue(value instanceof Set);
// %inferred-type: Set<any>
;
value }
引数value instanceof Set
を型ガードと同様に使用していますが、条件文の一部をスキップする代わりに、false
が例外をトリガーします。
asserts «arg» is «type»
次の例では、アサーションシグネチャasserts value is number
は、パラメータvalue
が型number
でなければならないことを示しています。そうでない場合は、例外がスローされます。
function assertIsNumber(value: any): asserts value is number {
if (typeof value !== 'number') {
new TypeError();
throw
} }
今回は、アサーション関数を呼び出すと、引数の型が絞り込まれます
function func(value: unknown) {
assertIsNumber(value);
// %inferred-type: number
;
value }
関数addXY()
は、既存のオブジェクトにプロパティを追加し、それに応じて型を更新します
function addXY<T>(obj: T, x: number, y: number)
: asserts obj is (T & { x: number, y: number }) {
// Adding properties via = would be more complicated...
.assign(obj, {x, y});
Object
}
= { color: 'green' };
const obj addXY(obj, 9, 4);
// %inferred-type: { color: string; } & { x: number; y: number; }
; obj
交差型S & T
は、型S
と型T
の両方のプロパティを持ちます。
function isString(value: unknown): value is string {
=== 'string';
return typeof value }
value is string
boolean
asserts «cond»
function assertTrue(condition: boolean): asserts condition {
if (!condition) {
new Error(); // assertion error
throw
} }
asserts condition
void
、例外asserts «arg» is «type»
function assertIsString(value: unknown): asserts value is string {
if (typeof value !== 'string') {
new Error(); // assertion error
throw
} }
asserts value is string
void
、例外アサーション関数は、既存の値の型を絞り込みます。強制変換関数は、新しい型を持つ既存の値を返します。次に例を示します
function forceNumber(value: unknown): number {
if (typeof value !== 'number') {
new TypeError();
throw
};
return value
}
: unknown = 123;
const value1a// %inferred-type: number
= forceNumber(value1a);
const value1b
: unknown = 'abc';
const value2.throws(() => forceNumber(value2)); assert
対応するアサーション関数は次のようになります
function assertIsNumber(value: unknown): asserts value is number {
if (typeof value !== 'number') {
new TypeError();
throw
}
}
: unknown = 123;
const value1assertIsNumber(value1);
// %inferred-type: number
;
value1
: unknown = 'abc';
const value2.throws(() => assertIsNumber(value2)); assert
強制変換は、アサーション関数の用途以外にも使用できる汎用的な手法です。たとえば、次のように変換できます
詳細については、[コンテンツは含まれていません]を参照してください。
次のコードについて考えてみましょう
function getLengthOfValue(strMap: Map<string, string>, key: string)
: number {
if (strMap.has(key)) {
= strMap.get(key);
const value
// %inferred-type: string | undefined
; // before type check
value
// We know that value can’t be `undefined`
if (value === undefined) { // (A)
new Error();
throw
}
// %inferred-type: string
; // after type check
value
.length;
return value
}-1;
return }
A行で始まるif
文の代わりに、アサーション関数を使用することもできます
assertNotUndefined(value);
そのような関数を記述したくない場合は、例外をスローするのが簡単な代替手段です。アサーション関数を呼び出すのと同様に、この手法も静的型を更新します。
@hqoss/guards
: 型ガード付きライブラリライブラリ@hqoss/guards
は、TypeScript用の型ガードのコレクションを提供します。次に例を示します
isBoolean()
、isNumber()
などisObject()
、isNull()
、isFunction()
などisNonEmptyArray()
、isInteger()
など