1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

型パズルメモ

Last updated at Posted at 2024-11-18

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
1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?