📖
TypeScriptで引数によって戻り値の型を変えてみた【条件付き型 × ジェネリクス】
本記事では、条件付き型(Conditional Types) と ジェネリクス を使って引数の値に応じて返す型を変える方法を紹介します。
やりたいこと
例えば "dog" という引数が渡されたら Dog 型を返し、"cat" の場合は Cat 型を返すような関数を作りたい。
条件付き型で定義する
まずは型を定義します。
type Dog = { kind: "dog"; bark(): void };
type Cat = { kind: "cat"; meow(): void };
export type AnimalType<T> = T extends "dog"
? Dog
: T extends "cat"
? Cat
: never;
ここでは T が "dog" なら Dog、"cat" なら Cat を返すという条件付き型を定義しています。
ジェネリクス関数で使う
function createAnimal<T extends "dog" | "cat">(
type: T
): AnimalType<T> {
if (type === "dog") {
return { kind: "dog", bark: () => console.log("woof") } as AnimalType<T>;
} else if (type === "cat") {
return { kind: "cat", meow: () => console.log("meow") } as AnimalType<T>;
} else {
throw new Error("Unknown animal type");
}
}
<T extends "dog" | "cat"> のように型パラメータを導入し、AnimalType<T> を戻り値の型に指定して、引数 type に応じた返り値の型を返します。
呼び出す
const dog = createAnimal("dog");
dog.bark(); // OK
dog.meow(); // ❌ エラーになる
const cat = createAnimal("cat");
cat.meow(); // OK
cat.bark(); // ❌ エラーになる
どんな時に使える?
- APIの種類やイベントの種類ごとに、返却される型が異なるとき
- フォームや入力の種類によって送信データの構造が変わるようなとき
- コードの安全性を保ちながら共通関数にロジックを集約したいとき
利用を避けたほうがいいケース
- 型の分岐が非常に多く、読みづらくなる場合(2〜3つくらいがちょうど良いと思う)
- 返却される型が複雑で、関数の中で条件ごとの実装を維持するのが困難なとき
- チームにジェネリクスや条件付き型に不慣れなメンバーが多いとき
まとめ
- T extends A ? X : Y のように 動的に型を切り替えるテクニックは、柔軟で型安全
- 実務ではAPIの種類やイベントの種類などに応じて、返り値の型を切り替えるのに便利
- ただし、使いすぎるとコードが読みにくくなるため、バランスよく使おう
ちょっとニッチなテーマでしたが、読んでくださりありがとうございました。

NCDC株式会社( ncdc.co.jp/ )のエンジニアチームです。 募集中のエンジニアのポジションや、採用している技術スタックの紹介などはこちら( github.com/ncdcdev/recruitment )をご覧ください! ※エンジニア以外も記事を投稿することがあります
Discussion