【入門編】 再帰的なConditional Types – TypeScript実践ガイド

こんにちは!TypeScriptの世界へようこそ。チーフアーキテクトの私です。

「型定義」って聞くだけで、なんだか堅苦しくて、エラー画面に怯えてしまう……そんな時期、誰にでもあります。特に、オブジェクトの中にオブジェクトが入り組んで、さらにその中にも……なんて構造を見ると、思わずブラウザのタブをそっと閉じたくなる気持ち、痛いほどよく分かります。

今回は、そんな複雑なネスト(入れ子)構造を華麗に、そして美しく料理するための奥義「再帰的なConditional Types(条件付き型)」についてお話をさせてください。

難しい言葉に聞こえますが、大丈夫ですよ。一つひとつ、身近な例えから紐解いていきましょう。

—

そもそも「再帰(さいき)」ってなに?

プログラムの世界での「再帰」とは、一言で言うと「自分の中に自分をもう一度登場させること」です。

ロシアの伝統民芸品である「マトリョーシカ」を思い浮かべてみてください。大きな人形を開けると、中から一回り小さな人形が出てくる。その中を開けると、さらに小さな人形が出てくる……。あの構造そのものが再帰です。

TypeScriptの型定義における再帰もこれと全く同じ。「大きなオブジェクトの皮を一枚めくったら、中身に対しても全く同じ型チェックのルールをもう一度適用する」という仕組みを作ることができるんです。

—

なぜ「再帰的な型」が必要なの?(お買い物の例え)

例えば、あなたがオンラインショップの「お買い物カゴ」のシステムを作っているとします。

お買い物カゴの中には、商品が入っていますよね。

type CartItem = {
name: string;
price: number;
};

ここまでは普通の型定義です。でも、もしそのカゴの中に「別のカゴ(ギフトボックスなど)」がすっぽり入っていたらどうでしょう? ギフトボックスの中にも商品が入っていて、さらにその中に……なんて無限の入れ子構造が想像できます。

こういう時、普通の型定義だと「3階層目までしか対応できません!」といった限界が来てしまいます。
そこで登場するのが、「何階層の深さになろうとも、終わりまで自動的に潜っていって処理してくれる」再帰的な型なのです。

—

実践!`DeepPartial`を作ってみよう

実務で一番よく遭遇し、かつ最も感動する再帰のユースケースが、この `DeepPartial`(ディープ・パーシャル) です。

通常の `Partial` は、オブジェクトの「1階層目」のプロパティをすべてオプショナル(省略可能、`?` 付き)にしてくれます。しかし、2階層目、3階層目にあるオブジェクトのプロパティまではオプショナルにしてくれません。

「すべての階層のプロパティを、丸ごとオプショナルにしたい!」
そんな願いを叶えるマトリョーシカ型を、一緒に組んでみましょう。

優しいコード解説付きサンプル

以下のコードを、あなたのエディタ(VS Codeなど)にそのまま貼り付けてみてください。

/

  • 【再帰的DeepPartialの定義】
  • T がオブジェクトだったら、中のプロパティすべてに再帰的に Partial を適用する。
  • オブジェクトじゃなければ、そのままの型を返す。

/
type DeepPartial = T extends object
? {
// Tのすべてのプロパティ K を取り出す
[K in keyof T]?: DeepPartial;
// 注目!ここで自分自身(DeepPartial)をもう一度呼び出しています(これがマトリョーシカ構造!)
}
: T; // オブジェクトじゃなければ(文字列や数値などなら)そのまま返す

// — ここから下がテスト用のお肉(データ)です —

// 複雑なネスト(入れ子)構造を持つユーザーの型定義
type UserProfile = {
id: number;
name: string;
address: {
zipCode: string;
city: {
name: string;
town: string;
};
};
};

// 通常の Partial だと、addressの中身(cityなど)はオプショナルになりません。
// しかし、DeepPartial を使うと……?
const updateData: DeepPartial = {
// id や name を書かなくても怒られない!
address: {
city: {
// なんと、一番深い town だけをピンポイントで省略してもエラーにならない!
name: “東京都”,
},
},
};

console.log(updateData);

コードのここがポイント!

1. `T extends object`
「もし渡された型 `T` がオブジェクトだったら〜」という条件分岐(Conditional Types)です。これが判定の第一歩。
2. `[K in keyof T]?`
オブジェクトのキーを一つずつ舐めていき、後ろの `?` で「すべてオプショナル(省略可能)」に書き換えています。
3. `DeepPartial` (ここが魔法の瞬間)
プロパティの中身(`T[K]`)に対して、もう一度 `DeepPartial` を適用しています。これが自分自身を呼び出す「再帰」の正体です。もし中身がさらにオブジェクトなら再びバラし、プリ型(stringやnumberなど)なら `:T` によってそのまま返されます。

これで、何階層深くても、一網打尽にすべてのプロパティをオプショナルにできる最強の型が完成しました。

—

つまずきやすいポイントと、温かいアドバイス

再帰的な型を書き始めると、誰もが一度は通る「恐怖の壁」があります。

1. 「無限ループの夢」を見たら

「おいおい、自分自身を無限に呼び出し続けたら、ブラウザやエディタがフリーズしちゃうんじゃないの?」と心配になるかもしれませんが、安心してください。TypeScriptのコンパイラは非常に優秀で、型の深さには一定の制限(最大奥行き)が設けられています。実用的なネストの深さであれば、フリーズする心配はまずありません。

2. エラーメッセージが呪文のように見える

再帰の中でエラーが起きた時、赤色の波線のエラーメッセージがものすごく長くなることがあります。
「うわ、もう無理……」と投げ出したくなったら、深呼吸をしてください。大体の原因は、`extends object` の判定漏れか、プリミティブ型(配列やnullなど)の考慮漏れです。
そんな時は、一番外側の型から一歩ずつ、`T` に何が入ってきているのかを `type Test = …` で切り出して確認していくのが、ベテランでもやる一番確実なデバッグ方法です。

—

おわりに

再帰的なConditional Typesは、初学者の方にとっては少しだけステップが高く感じられるかもしれません。ですが、これはTypeScriptという道具が持つ、最も美しく、そして実務で私たちを救ってくれる強力な魔法の一つです。

最初は「ふーん、マトリョーシカみたいでおもしろいな」くらいの感覚で十分です。
ご自身のプロジェクトで「あ、ここ何階層もあって型をつけるのが面倒だな……」と思った時に、そっとこのブログの記事やコードを思い出してもらえたら、アーキテクトとしてこれほど嬉しいことはありません。

あなたのTypeScriptライフが、より快適で楽しいものになりますように。
それではまた、次の現場でお会いしましょう!

コメント

タイトルとURLをコピーしました