mcpField
T
template-literal-types

template-literal-types

github.com/marius-townhouse開発646 回インストール39 週間前に更新
リポジトリ

template-literal-types について

テンプレートリテラル型で構造化文字列を表し、DSLの解析や文字列型の変換を行うTypeScriptスキルです。文字列を多用するAPIにコンパイル時の検査を加えたい開発者向けです。

主な機能

  1. 01構造化文字列パターンの表現
  2. 02`infer` による文字列解析
  3. 03再帰的な文字列型変換
  4. 04コンパイル時の形式検証
  5. 05マップ型との組み合わせ

活用シーン

  1. 01ID、パス、URLの型安全性向上
  2. 02CSSセレクターやクエリ言語の解析
  3. 03snake_caseからcamelCaseへの変換
  4. 04APIの文字列形式の検証

インストール

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

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill template-literal-types

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

詳細

テンプレートリテラル型で DSL と文字列間の関係をモデル化する

概要

テンプレートリテラル型を使うと、JavaScript のテンプレートリテラルの機能を TypeScript の型システムで活用できます。構造化された文字列のサブセットをモデル化したり、ドメイン固有言語(DSL)を解析したり、文字列型間の関係を表現したりできます。条件型や infer キーワードと組み合わせることで、型レベルで高度な文字列操作を行えます。

このスキルは、文字列を多用する API に型安全性をもたらし、強力な型変換を構築するうえで不可欠です。

このスキルを使う場面

  • 構造化された文字列パターン(ID、パス、URL)のモデル化
  • ドメイン固有言語(CSS セレクター、クエリ言語)の解析
  • 文字列型の変換(camelCase、snake_case の変換)
  • コンパイル時の文字列形式の検証
  • キー変換のためのマップ型との組み合わせ

絶対ルール

テンプレートリテラル型を使って、構造化された文字列のサブセットや DSL をモデル化します。解析には infer を、変換にはマップ型を組み合わせます。

検出

次のような機会に注目します。

// RED FLAGS - Untyped strings that could be precise
type EventType = string;  // Could be 'click' | 'hover' | etc.
function query(selector: string): Element;  // Could parse CSS selectors
type CSSProperty = string;  // Could validate property names

基本的なテンプレートリテラル型

// Match strings starting with a prefix
type PseudoString = `pseudo${string}`;
const science: PseudoString = 'pseudoscience';  // OK
const alias: PseudoString = 'pseudonym';        // OK
const physics: PseudoString = 'physics';        // Error!

// Match specific patterns
type DataAttribute = `data-${string}`;
type HTTPSUrl = `https://${string}`;
type VersionString = `v${number}.${number}.${number}`;

テンプレートリテラルを使ったインデックスシグネチャ

// Allow data-* attributes while keeping type safety
interface Checkbox {
  id: string;
  checked: boolean;
  [key: `data-${string}`]: unknown;
}

const check: Checkbox = {
  id: 'subscribe',
  checked: true,
  'data-listIds': 'all-the-lists',  // OK
  value: 'yes',  // Error: not data-* and not known property
};

infer を使った解析

条件型と infer を使って文字列の一部を抽出します。

// Extract event name from handler type
type EventName<T> = T extends `on${infer Name}` ? Name : never;

type ClickEvent = EventName<'onClick'>;      // 'Click'
type HoverEvent = EventName<'onMouseEnter'>; // 'MouseEnter'
type BadEvent = EventName<'handleClick'>;   // never

// Extract path parameters
type PathParams<T> = T extends `/users/${infer UserId}/posts/${infer PostId}`
  ? { userId: UserId; postId: PostId }
  : never;

type Params = PathParams<'/users/123/posts/456'>;
// { userId: '123'; postId: '456' }

文字列変換

文字列を変換する再帰型を構築します。

// Convert snake_case to camelCase
type CamelCase<S extends string> =
  S extends `${infer Head}_${infer Tail}`
    ? `${Head}${Capitalize<CamelCase<Tail>>}`
    : S;

type T1 = CamelCase<'foo'>;           // 'foo'
type T2 = CamelCase<'foo_bar'>;      // 'fooBar'
type T3 = CamelCase<'foo_bar_baz'>;  // 'fooBarBaz'

// Apply to object keys
type CamelCaseKeys<T> = {
  [K in keyof T as CamelCase<K & string>]: T[K]
};

type SnakeCase = { user_name: string; email_address: string };
type Camel = CamelCaseKeys<SnakeCase>;
// { userName: string; emailAddress: string }

実例:CSS セレクター

// Enhance querySelector with precise types
type HTMLTag = keyof HTMLElementTagNameMap;

declare global {
  interface ParentNode {
    // Simple tag selector
    querySelector<TagName extends HTMLTag>(
      selector: TagName
    ): HTMLElementTagNameMap[TagName] | null;
    
    // Tag#id selector
    querySelector<TagName extends HTMLTag>(
      selector: `${TagName}#${string}`
    ): HTMLElementTagNameMap[TagName] | null;
  }
}

// Usage
const img = document.querySelector('img#hero');
// Type: HTMLImageElement | null
// Can access img?.src, img?.alt, etc.

const div = document.querySelector('div#container');
// Type: HTMLDivElement | null

マップ型との組み合わせ

// Create event handler types from event names
type EventMap = {
  click: MouseEvent;
  keydown: KeyboardEvent;
  submit: SubmitEvent;
};

type EventHandlers<Events extends Record<string, Event>> = {
  [K in keyof Events as `on${Capitalize<K & string>}`]?: 
    (event: Events[K]) => void;
};

type Handlers = EventHandlers<EventMap>;
// {
//   onClick?: (event: MouseEvent) => void;
//   onKeydown?: (event: KeyboardEvent) => void;
//   onSubmit?: (event: SubmitEvent) => void;
// }

プレッシャー耐性プロトコル

単純な string 型を使うよう求められた場合:

  1. パターンを特定する:文字列にはどのような構造がありますか?
  2. シンプルに始める:まずリテラル型のユニオンを使います
  3. テンプレートを追加する:無限だが構造化された集合にはテンプレートリテラルを使います
  4. 解析を検討する:情報の抽出には infer を使います
  5. エッジケースをテストする:型が単に厳密なだけでなく、正確であることを確認します

注意すべき点

アンチパターン問題点
type ID = string検証の機会を逃します
テストされていない複雑なテンプレート型不正確な可能性があります
逃げ道のない解析複雑なセレクターにはフォールバックが必要です
厳密すぎる型正当なユースケースを壊す可能性があります

よくある正当化

「string で十分です」

実際には:テンプレートリテラル型を使えば、コンパイル時にタイプミスや無効な形式を検出できます。'user-123' と 'users-123' の違いをすぐに検出できます。

「複雑すぎます」

実際には:まずはシンプルなプレフィックスパターンから始め、必要に応じて複雑さを加えていきます。基本的なテンプレートリテラルにも効果があります。

「パフォーマンスに影響します」

実際には:テンプレートリテラル型はコンパイル時に評価されます。実行時のコストはありません。

クイックリファレンス

パターン構文ユースケース
プレフィックス`data-${string}`data 属性
サフィックス`${string}Event`イベント名
中間`${string}.${string}`ファイル拡張子
抽出`on${infer Name}`解析
変換`${Head}${Capitalize<Tail>}`camelCase

まとめ

テンプレートリテラル型は、文字列を多用するコードに型安全性をもたらします。構造化された文字列のモデル化、DSL の解析、型の変換に活用します。infer やマップ型と組み合わせることで、型レベルで強力な文字列操作を実現できます。

参考資料

  • Dan Vanderkam 著『Effective TypeScript』第2版
  • 項目54:テンプレートリテラル型を使って DSL と文字列間の関係をモデル化する

コメント