Gist にしたためていたメモがいよいよ読みにくくなったので転載。
やや古いものや、 satisfies
も指定したほうがいいものも有るのでそのうち修正予定。
引数の値に応じて関数の型をいい感じに推論させてほしい
// こんな関数があって
const test = (str: string, isNumber = false): string | number => {
if (isNumber) {
return Number(str);
}
return str;
};
普通に呼びだすとこうなる
const t1 = test('aa'); // t1 = string | number
const t2 = test('aa', true); // t2 = string | number
これをこうしたい
const t1 = test('aa'); // t1 = string
const t2 = test('1', true); // t2 = number
これはよく出てくる
const test = <T extends boolean>(str: string, isNumber: T = false as T): T extends true ? number : string => {
if (isNumber) {
return <T extends true ? number : string>Number(str);
}
return <T extends true ? number : string>str;
};
const t1 = test('aa'); // t1 = string | number ...と、 union になったまま
const t2 = test('1', true); // t2 = number
const t3 = test('1', false); // t3 = string ...と、引数指定すればいい感じにはなる
このときはこう
const test = <T extends boolean = false>(str: string, isNumber: T = false as T): T extends true ? number : string => {
// ~~~~~~~ ここが大事
if (isNumber) {
return <T extends true ? number : string>Number(str);
}
return <T extends true ? number : string>str;
};
利用例
const t1 = test('aa'); // t1 = string
const t2 = test('1', true); // t2 = number
// 当たり前だけど test の第一引数に数値以外を渡したら型パズル的にはOKだがランタイムエラーになるので注意
Union
構造体や配列から union type を作るやつ。
const genders = {
Male: 'male',
Female: 'female',
Other: 'other',
} as const;
type Gender = typeof genders[keyof typeof genders]; // => 'male' | 'female' | 'other'
const prefs = ['北海道', '青森県', '岩手県', '宮城県', '福島県', '秋田県', '山形県', '略'] as const;
type Pref = typeof prefs[number]; // Pref => '北海道' | '青森県' | 略;
Jest でテストコードでよく使うもの
こんなクラスがあって
class Hoge {
huga(piyo: string): string {
return `${piyo}.${piyo}`;
}
}
これから↓の型を取り出したい
type Huga = (piyo: string) => string;
このときはこう
type StripMethodType<T extends {}, M extends keyof T> =
T[M] extends (...args: any) => any ? T[M] : never;
type MethodType = StripMethodType<Hoge, 'huga'>;
// これでもよい。こっちのほうが使いやすいかも
type MethodType = Hoge['huga'];
Hoge.huga
の引数の型を取り出したいなら Parameters
を使えばよい
type MethodParam<T extends {}, M extends keyof T> =
Parameters<StripMethodType<T, M>>;
type Params = MethodParam<Hoge, 'huga'>; // => [piyo: string]
Mock の resolvedValue とかの戻り値をいい感じにしたい
(hugaMock as jest.Mock).mockReturnValue('a.a');
// ~~~~ 文字列以外のときは怒ってほしいので↓
(hugaMock as jest.Mock<ReturnType<StripMethodType<Hoge, 'huga'>>>).mockReturnValue('a.a');
長ったらしいのでこう
type JestMockReturnType<T extends {}, M extends keyof T> =
T[M] extends (...args: any) => any ? jest.Mock<ReturnType<T[M]>> : never;
(hugaMock as JestMockReturnType<Hoge, 'huga'>).mockReturnValue('a.a');
引数に応じた処理を加えたいならこう
type Param = MethodParam<Hoge, 'huga'>;
(hugaMock as JestMockReturnType<Hoge, 'huga'>)
.mockImplementation((piyo: Param[0]) => `${piyo}.${piyo}`);
ちなみに単純な string ではなく以下のようにテンプレートリテラルで表すことができる
type HugaTemplateLiteral = `${string}.${string}`;
const huga: HugaTemplateLiteral = 'aa'; // => Type '"aa"' is not assignable to type '`${string}.${string}`'
const hoge: HugaTemplateLiteral = 'a.a'; // OK!
class Responder {
ok<T>(data: T, response: Express.Response): void {
response.status(200).json({ data });
}
}
これから↓の型を取り出したい。つまり、 Generics で利用時に型を確定させたい(T
に何かしらの制約があり、動的に取り出したい
type okProps<T> = [T, Express.Response];
この記法は文法エラーなので注意
type CannotCapture = Parameters<Responder['ok']<{ foo: string }>>;
この場合は、三角跳びでキャプチャできた
type ResponderOkPrototype<T> = typeof Responder.prototype.ok<T>;
type ResponderOkProps = Parameters<ResponderOkPrototype<{ foo: string }>>;
Set / Map / Record から型を取り出す方法
実践 TypeScript に書いてあった Set の値の型を取り出す型
type PickSet<T> = T extends Set<infer I> ? I : never;
実践 TypeScript に書いてあった Map の Key を取り出す型
type PickMapKeys<T> = T extends Map<infer K, any> ? K : never;
(自分が本当に欲しかった) Record の Type の型を取り出す型
type PickRecordValueType<T> = T extends Record<any, infer U> ? U : never;
利用例
type ComplicatedTypeRecord = Record<string, ComplicatedType>;
type PickedRecordValueType = PickRecordValueType<ComplicatedTypeRecord>;
// => PickedRecordValueType: ComplicatedType
Record の Key の型を取り出す型
type PickRecordKeyType<T> = T extends Record<infer K, any> ? K : never;
利用例
type ComplicatedTypeRecord = Record<UnionType, string>;
type PickedRecordKeyType = PickRecordKeyType<ComplicatedTypeRecord>;
// => PickedRecordKeyType: UnionType