
TS上級者への進化をまとめた、三段階の概念を学習していきます。
そもそも、TSの上級者ってどんなの?という人は是非今回のブログを読んで、レベルアップしましょう。
TypeScriptの成長ステップ
1. 型を作る
↓
2. 型を読む
↓
3.型を推論する
実務でもこのステップを踏むと一気に書けるコードの質が上がること間違いなしなのです。
1. 型を作る(初級)
まずは 自分で型を書き始める段階です。
・type / interface を使いこなす
・関数の引数・戻り値を明示していく
・DTO / Domain / Props を分ける
・Union / Intersection / Literal を使う
サンプルコード
type User = {
id: number;
name: string;
email?: string;
}
この時点ではまだ、「型=データ構造の説明」として扱っているだけで大丈夫です。
2. 型を読む(中級)
他人の作った型を理解する能力は実務で最も重要になります。
JSプロレベルの人でも、TSの型は最初「呪文」のように見えるんです。
type ExtractProps<T> = T extends React.ComponentType<infer P> ? P : never;
例えば、このコードを見たときに、この意味が読めるかどうかでTS理解に大きな差がつきます。
ポイント解説
・extends は「条件分岐」
・infer は「推論して取り出す」
・Mapped Types の構文 { [K in keyof T]: ... }
・Utility Types の中身を追う
これが理解できるようになると、
React・Next.js・Zod・Axios・Prisma の型が読み解けるようになり、
ライブラリ内部の意図が理解できるようになるハズです。
3.型を推論する(上級)
自分が書くべき型を、TSに推論させる方向に、
シフトするのが最終段階です。
これはどういうことかというと、「型を書く量」より「型を導かせる設計」を意識します。
「型を手書きする」のではなく、「関数・構造をどう作れば TypeScript が正しい型を推論するか」を考えるという事になります。
サンプルコード 1
推論主導の書き方
const createUser = (id: number, name: string) => ({
id,
name,
createdAt: new Date()
});
const u = createUser(1, "Alice");
// → TSが { id: number; name: string; createdAt: Date } を自動推論
このコードでは、型を書いてないのに型が出来上がります。
サンプルコード 2
ジェネリクスで推論を誘導する
function wrap<T>(value: T) {
return { value };
}
const w = wrap({ a: 1, b: "test" });
// → { value: { a: number; b: string } }
ジェネリクスは、最初書く意味がわかりにくいのですが、こういうコードが書けるようになると、上級レベルの「推論しやすいAPIの形にする設計」に到達します。
上級レベルポイント
・引数をオプション化しすぎない
・カリー化で推論精度を上げる
・オブジェクトを返すパターンを使う
・Overloadよりジェネリクスを活かす
簡単フェーズまとめ
1. 型を“作る”
自分で型を設計する
関数・オブジェクトへ型を付ける
Union / Intersection に慣れる
2. 型を“読む”
ライブラリの型を読める
extends / infer / Mapped Types を理解
「型で書かれた仕様」を読み解ける
3. 型を“推論する”
TSが型を導くように書き方を変える
「書く型」より「推論させる型」を重視
型設計 → API設計 → コード設計が繋がる
一言で言うと、最初は「型を書く」だが、最終的には「型が自然に生まれる設計をする」ことがゴール。
ここまでできるようになると、TSはもはやただの言語ではなく、
設計の補助輪・品質保証の仕組みとして機能するようになる。
inferの理解
TSのソースが一気に読めるようになる、inferについて個人的にまだ理解が薄かったので、少し学習して見たいと思います。
「infer」というのは、
型の一部を変数として抜き出すための仕組みです。
そして、
抜き出された型を
型変数として再利用することができると理解しましょう。
infer の本質
・infer X は「この位置に現れる型を X に代入してほしい」という指示
・infer は 条件分岐の内部でのみ使える
・infer の結果は 成功したときだけ型として利用可能
・失敗したら never になる(=マッチ失敗)
サンプルコード 1. 関数の戻り値の型を取り出す
type Return<T> = T extends (...args: any) => infer R ? R : never;
type A = Return<() => number>; // number
type B = Return<(x: string) => boolean>; // boolean
解説
"(...args: any) => infer R" という関数型のパターンに一致したら、
その
戻り値部分を"R"として捕まえて返すという動作。
「関数の構造を分解して、中身の型を抽出」しています。
サンプルコード 2. 配列の中身の型を抽出
type Element<T> = T extends (infer U)[] ? U : never;
type A = Element<number[]>; // number
type B = Element<string[]>; // string
type C = Element<boolean>; // never(マッチしない)
解説
・(infer U)[] というパターンに一致すれば U が取れる
・boolean は配列ではないので、never
Array<T> の T を抜き出す処理。
サンプルコード 3. タプルから最初の要素だけ取り出す
type First<T> =
T extends [infer Head, ...any[]]
? Head
: never;
type A = First<[1, 2, 3]>; // 1
type B = First<[]>; // never
ポイント
・型レベルで“構造分解”できている
サンプルコード 4. Promiseの中身の型を取得する
type Awaited<T> =
T extends Promise<infer U>
? U
: T;
type A = Awaited<Promise<string>>; // string
type B = Awaited<Promise<number[]>>; // number[]
type C = Awaited<string>; // string(Promiseじゃないのでそのまま)
ポイント
JSの async/await の動作を型として表現
サンプルコード 5. APIレスポンス → 型抽出
type ResponseData<T> =
T extends { data: infer D } ? D : never;
type A = ResponseData<{ data: { id: 1 } }>;
// => { id: 1 }
type PropsOf<T> =
T extends React.ComponentType<infer P> ? P : never;
ポイント
React TS では超頻繁に出てくる
サンプルコード 6. 再帰×infer
type DeepAwaited<T> =
T extends Promise<infer U>
? DeepAwaited<U>
: T;
type X = DeepAwaited<Promise<Promise<number>>>;
// => number
ポイント
型の木構造を辿りながら中身を取り出す。
あとがき
TSの奥深さが理解できるようになると、なんだか面白みを感じてきませんか?
人はわからないことが少しだけわかるようになると、探究心という好奇心を伴うワクワク感が生まれてきます。
これを拡大させるのも、忘れさせるのも自分次第ですが、ちょっとした興味を深掘りできるようになると、物事をなんでも解決できるいいエンジニアにレベルアップしていくことができるようになります。
コレ、ものづくりの基本ですからね。
次回は、最終フェーズの「エコシステムの理解」について学習を進めていきたいと思います。
0 件のコメント:
コメントを投稿