mcpField
T
typescript-advanced

typescript-advanced

github.com/trancong12102開発745 回インストール5 週間前に更新
リポジトリ

typescript-advanced について

高度なTypeScriptパターンとtype-festのユーティリティを活用し、安全で保守しやすいコードを書くためのスキルです。TypeScript開発者やコーディングエージェントを対象に、型機能をいつ、どう使うかを重視し、API全般のリファレンスとは異なる構成です。

主な機能

  1. 01unknownとneverによる型安全性
  2. 02判別可能なユニオンと網羅性チェック
  3. 03ブランド型による名目的な安全性
  4. 04satisfiesとNoInferによる推論制御
  5. 05type-festユーティリティの選択
  6. 06よくある型の落とし穴への指針

活用シーン

  1. 01IDや検証済み値の安全な型付け
  2. 02型付き設定やレジストリーの構築
  3. 03ユニオンの全バリアントを網羅的に処理
  4. 04複雑な型変換に使うユーティリティの選択

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/trancong12102/agentskills --skill typescript-advanced

インストール後、Claude Code で/typescript-advancedを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
conditional-types.md
mapped-types.md
module-augmentation.md
template-literals.md
type-fest.md

詳細

TypeScript 上級:パターン、落とし穴、type-fest

このスキルでは、高度な TypeScript を記述する際のルール、規約、アーキテクチャ上の決定を定めています。型レベルでよくあるバグを防ぎ、安全で保守しやすいコードにつながるパターンを徹底するため、意図的に特定の方針を採用しています。

TypeScript の機能に関する詳細な API ドキュメントは、適切なほかのツール(ドキュメント検索、ウェブ検索など)を利用してください。このスキルでは、高度な型機能をいつ、なぜ、どのように正しく使うかに焦点を当てています。

型安全性の考え方

any、unknown、never の違い — 覚えておくべき唯一のルール
型代入元代入先操作用途
any何でも何でもすべて(安全でない)新しいコードでは使わない
unknown何でもunknown / any のみなし(絞り込み前)外部入力、JSON、ユーザーデータ
never何もない何でもなし網羅性チェック、到達不能なコード

ルール:新しいコードでは any を決して使わないでください。 外部との境界では unknown を使い、操作する前に型を絞り込んでください。網羅性の確保と不可能な状態の表現には never を使います。

enum よりユニオン型を優先する
// Avoid — numeric enums are structurally assignable to number (footgun)
enum Direction {
  Up,
  Down,
  Left,
  Right,
}
function go(d: Direction) {}
go(42); // no error — TypeScript allows any number!

// Prefer — exhaustive, tree-shakeable, no runtime artifact
type Direction = "up" | "down" | "left" | "right";

文字列 enum は数値 enum より安全ですが、それでも実行時のオーバーヘッドやインポート時の扱いにくさがあります。逆引きが必要な場合を除き、文字列リテラルのユニオン型をデフォルトとして選びます。

interface と type — 選択表
シナリオ使用する型理由
オブジェクトの形状、クラスの契約interface宣言のマージに対応し、エラーメッセージも分かりやすい
ユニオン、交差、マップ型/条件型typeこれらに対応しているのは type のみ
サードパーティの拡張が必要interface宣言のマージに対応しているのは interface のみ
パブリック API の型(ライブラリ)interface利用者が拡張でき、ツールチップでの表示も分かりやすい
内部の計算型typeより表現力が高く、意図しないマージも起きない

判別可能なユニオン型と網羅性チェック

never による網羅性確保のパターン

判別可能なユニオン型に対するすべての switch / if-else チェーンでは、各バリアントを処理する必要があります。 新しいバリアントが追加されたときにコンパイル時エラーを発生させるには、never への代入を使います。

type Result<T> =
  | { status: "ok"; data: T }
  | { status: "error"; error: Error }
  | { status: "loading" };

function handle<T>(result: Result<T>): string {
  switch (result.status) {
    case "ok":
      return JSON.stringify(result.data);
    case "error":
      return result.error.message;
    case "loading":
      return "Loading...";
    default:
      const _exhaustive: never = result;
      return _exhaustive; // compile error if a variant is unhandled
  }
}
判別可能なユニオン型のルール
  • 判別プロパティはリテラル型にする必要があります — string、number、boolean のリテラルを使います。string のような広い型では絞り込まれません。
  • すべてのメンバーで判別プロパティ名を統一してください(kind、type、status など)。
  • 判別プロパティをオプショナルにするのは避けてください — status?: "ok" | "error" は型の絞り込みを壊します。

ブランド型 — 構造的型付けシステムにおける名目的な安全性

TypeScript は構造的型付けです。デフォルトでは、UserId(string)と OrderId(string)は相互に代入できます。ブランド型を使うと、実行時のオーバーヘッドなしに型レベルでこれを防げます。

推奨パターン:unique symbol ブランド
declare const __brand: unique symbol;
type Brand<T, B> = T & { readonly [__brand]: B };

type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;

// Constructor = the single trust boundary, validate here
const toUserId = (id: string): UserId => id as UserId;
const toOrderId = (id: string): OrderId => id as OrderId;

function getUser(id: UserId) {
  /* ... */
}
getUser(toUserId("abc")); // ok
getUser(toOrderId("abc")); // ERROR — OrderId not assignable to UserId
getUser("abc"); // ERROR — string not assignable to UserId
ブランド型を使う場面
  • ID — UserId、OrderId、ProductId を使うと、相互に誤って代入するのを防げます
  • 単位 — Meters、Feet、USD、EUR を使うと、計算ミスを防げます
  • 検証済みの文字列 — Email、URL、Slug で、検証済みであることを表現できます
  • 不透明なトークン — JWTToken、APIKey を使うと、誤ってログに記録したり表示したりするのを防げます
type-fest を使う代替方法

複数のタグの合成やメタデータには、type-fest の Tagged<T, Tag> を使います。

import type { Tagged, GetTagMetadata } from "type-fest";
type UserId = Tagged<string, "UserId">;
type AdminId = Tagged<UserId, "Admin">; // composable — both tags preserved

最新の型推論ツール

satisfies — 型を広げずに検証する(TS 4.9 以降)
type Theme = Record<"primary" | "secondary", string | string[]>;

// Type annotation: loses specific types
const t1: Theme = { primary: "#000", secondary: ["#111", "#222"] };
t1.secondary.map((s) => s); // ERROR — string | string[] has no .map

// satisfies: validates structure, keeps specific inference
const t2 = { primary: "#000", secondary: ["#111", "#222"] } satisfies Theme;
t2.secondary.map((s) => s); // ok — inferred as string[]

satisfies を使う場面: キーのタイプミスを検出するために設定を検証しつつ、特定の値に対するオートコンプリートも必要な場合です。

const 型パラメーター — ジェネリックなリテラル型の推論(TS 5.0+)
// Without const: T = string[]
function routes<T extends string[]>(r: T): T {
  return r;
}

// With const: T = readonly ["users", "posts"]
function routes<const T extends string[]>(r: T): T {
  return r;
}
const r = routes(["users", "posts"]); // readonly ["users", "posts"]

const 型パラメーターを使う場面: レジストリー、設定ファクトリー、または呼び出し側でリテラル型を保持することが重要なジェネリックを構築する場合です。

NoInfer<T> — 推論元を制御する(TS 5.4+)

パラメーターが型推論に寄与するのを防ぎます。T を読み取りますが、T がどのような型になるかには影響しません。

function createFSM<const TState extends string>(config: {
  states: TState[];
  initial: NoInfer<TState>; // must be from states, can't introduce new values
}) {
  /* ... */
}

createFSM({ states: ["idle", "running"], initial: "idle" }); // ok
createFSM({ states: ["idle", "running"], initial: "stopped" }); // ERROR

NoInfer を使う場面: 関数に同じ型パラメーターを共有する複数のパラメーターがあり、そのうちの一つを新しい候補として推論に加えるのではなく、ほかのパラメーターから推論される型に制約したい場合です。


type-fest:車輪の再発明を避ける

type-fest は、実行時のコストがゼロ(型のみ)のユーティリティ型を200種類以上提供します。カスタムのユーティリティを書く前に、必ず type-fest を確認してください。

import type { Simplify, Merge, SetRequired, LiteralUnion } from "type-fest";
選択表:組み込み機能と type-fest
必要なもの組み込み機能type-fest
キーをオプション/必須にするPartial, RequiredSetOptional, SetRequired(キーごと)
ディープな部分型/読み取り専用型—PartialDeep, ReadonlyDeep
2つの型をマージする(交差ではなく上書き)—Merge, MergeDeep
可読性のために交差型を平坦化する—Simplify
オートコンプリート対応の string 型ユニオン—LiteralUnion
最低1つ/厳密に1つの制約—RequireAtLeastOne, RequireExactlyOne
名目的/ブランド付き型—Tagged, UnwrapTagged
JSON のラウンドトリップ型—Jsonify
厳密な omit(キーの存在が必須)Omit(緩い)Except(厳密)
ドット区切りの深いパスアクセス—Paths, Get
厳密なオブジェクト(余分なプロパティを拒否)—Exact
値の型による pick/omit—ConditionalPick, ConditionalExcept
Package.json/tsconfig の型—PackageJson, TsConfigJson
キーの大文字小文字変換—CamelCasedProperties など
特に使用頻度の高いユーティリティ
  • Simplify<T> — A & B & C を読みやすい { ...すべてのキー } に平坦化します。ホバー時のツールチップが読みにくくなる交差型に使用してください。
  • Merge<A, B> — キーが競合すると A & B は never になりますが、Merge はきれいに上書きします。型でキー名が重複する場合は、& の代わりに使用してください。
  • LiteralUnion<Literal, Base> — 'a' | 'b' | string はオートコンプリートを無効にしますが、LiteralUnion はそれを維持します。拡張可能な文字列 API に不可欠です。
  • SetRequired<T, K> / SetOptional<T, K> — 重複するインターフェースを維持せずに、特定のキーを切り替えます。
  • Jsonify<T> — JSON.parse(JSON.stringify(x)) の動作をモデル化します。Date → string、undefined → 削除、インターフェースのオープンインデックスに関する問題を検出します。

よくある落とし穴

  1. any が気付かないうちに漏れ出します — ひとつの any が、代入、ジェネリクス、戻り値の型へと伝播します。ユーティリティ型にひとつでも any があると、その後のすべての型が型安全ではなくなります。代わりに unknown と型の絞り込みを使用してください。

  2. 余分なプロパティのチェックはリテラルにしか適用されません — 変数を介して代入すると、余分なプロパティのチェックは完全に回避されます。実行時の安全性を確保するために、このチェックに頼らないでください。

    interface Point {
      x: number;
      y: number;
    }
    const obj = { x: 1, y: 2, z: 3 };
    const p: Point = obj; // no error — z slips through
    
  3. never に対する分配条件型 — T が never の場合、T extends X ? A : B は B ではなく never を返します。タプルでラップしてください:[T] extends [X]。

  4. Omit はキーの存在をチェックしません — Omit<T, "typo"> は何事もなく成功します。厳密にキーをチェックするには、type-fest の Except を使用してください。

  5. let による型の拡大 — let x = "hello" の型は "hello" ではなく string です。リテラル型を保持するには、const、as const、または satisfies を使用してください。

  6. キーが競合する場合の & による交差 — { a: string } & { a: number } では、a の型が never になります。代わりに type-fest の Merge を使用してください。

  7. 列挙型の数値代入可能性 — enum Foo { A, B } では、const x: Foo = 999 が許可されます。代わりに文字列リテラルのユニオンを使用してください。

  8. interface の意図しないマージ — 同じスコープに interface User {} の宣言がふたつあると、気付かないうちにマージされます。拡張されるべきではない内部型には type を使用してください。

  9. isolatedModules での const enum — esbuild、SWC、Babel はいずれも isolatedModules を使用します。.d.ts やライブラリコード内の const enum は、これらのビルドを壊します。

  10. 配列パラメーターでの readonly の付け忘れ — function f(arr: string[]) では変更が可能です。変更する意図のないパラメーターには readonly string[] を使用してください。

  11. 関数パラメーターの構造的部分型付け — メソッド構文の push(x: T) は双変です(型安全ではありません)。正しい分散を実現するには、strictFunctionTypes のもとで関数プロパティ構文 push: (x: T) => void を使用してください。

  12. type-fest ユーティリティを再発明する — DeepPartial、DeepReadonly、Merge、ブランド型、またはキー操作型を書く前に、type-fest を確認してください。このライブラリは、手作業で実装したものでは見落とされるエッジケース(循環参照、読み取り専用配列、マップ/セット)にも対応しています。


参考ファイル

特定のパターンを扱う際は、関連する参考ファイルを読んでください:

ファイル読むタイミング
references/conditional-types.mdinfer、分配条件型、extends による制約
references/mapped-types.mdキーの再マッピング、フィルタリング、テンプレートリテラルによるキー操作
references/template-literals.md型レベルでの文字列操作、パターンマッチング、解析
references/module-augmentation.md宣言のマージ、サードパーティ型の拡張、グローバルスコープ
references/type-fest.md使用例付きのカテゴリ別 type-fest ユーティリティ全一覧

コメント