mcpField
T
typescript-types

typescript-types

github.com/mkosirその他78540 回インストール8 週間前に更新
リポジトリ

typescript-types について

typescript-types は、現在のタスクに TypeScript の型規約を適用する Skill です。TypeScript コードを作成・レビューする開発者向けで、リポジトリ固有の規約を優先しつつ、型の絞り込み、不変性、不確かな値の安全な扱いを重視します。

主な機能

  1. 01リポジトリ規約に沿った型指針の適用
  2. 02型の絞り込みと推論の優先
  3. 03意図しない変更を防ぐ読み取り専用型
  4. 04テンプレートリテラル型による文字列制約
  5. 05曖昧な値への unknown の使用
  6. 06アサーションとエラー抑制の限定

活用シーン

  1. 01新しいコードに適した型表現の選択
  2. 02TypeScript の型安全性レビュー
  3. 03リポジトリ設定に合わせた型スタイルの調整
  4. 04サードパーティ型定義との不一致への対処

インストール

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

$npx skills add https://github.com/mkosir/typescript-style-guide --skill typescript-types

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

詳細

型

現在のタスクのコンテキストで、TypeScript スタイルガイドの型に関する規約を適用してください。

ワークフロー

  1. 対象のリポジトリの規約と設定を確認します。
  2. 明示的なリポジトリの規約がある場合は、この方針に基づくガイダンスより優先します。
  3. タスクに関連するガイダンスのみを適用、確認、または説明します。
  4. 適切なモデルがコンテキストや判断に左右される場合は、重要なトレードオフを示します。

適用範囲

  • 自動でチェックできる項目は、TypeScript と ESLint に任せてください。
  • このガイダンスが有効だからといって、無関係な TypeScript スタイルガイドの規約を持ち込まないでください。

型

型を作成するときは、コードをどのように最適に記述できるかを検討してください。
表現力を高め、型をできるだけ狭く保つことで、コードベースに次のような利点がもたらされます。

  • 型安全性の向上 — 型を狭めるとデータの形状や動作についてより具体的な情報を得られるため、コンパイル時にエラーを検出できます。
  • コードの明瞭さの向上 — データに明確な境界と制約を設けて認知負荷を軽減し、ほかの開発者にとって理解しやすいコードになります。
  • リファクタリングの容易化 — 型を狭くすると、コードの変更に伴うリスクが小さくなり、自信を持ってリファクタリングできます。
型推論

経験則として、型を狭めるのに役立つ場合にのみ、型を明示的に宣言してください。

型を追加する必要がないからといって、追加すべきでないとは限りません。場合によっては、型を明示的に宣言することでコードの可読性が向上し、意図が明確になります。

型を狭めるのに役立つ場合は、型を明示的に宣言してください。

// ❌ Avoid
const employees = new Map(); // Inferred as wide type 'Map<any, any>'
employees.set('Lea', 17);
type UserRole = 'admin' | 'guest';
const [userRole, setUserRole] = useState('admin'); // Inferred as 'string', not the desired narrowed literal type

// ✅ Use explicit type declarations to narrow the types.
const employees = new Map<string, number>(); // Narrowed to 'Map<string, number>'
employees.set('Gabriel', 32);
type UserRole = 'admin' | 'guest';
const [userRole, setUserRole] = useState<UserRole>('admin'); // Explicit type 'UserRole'

型を推論できる場合は、明示的な宣言を避けてください。

// ❌ Avoid
const userRole: string = 'admin'; // Inferred as wide type 'string'
const employees = new Map<string, number>([['Gabriel', 32]]); // Redundant type declaration
const [isActive, setIsActive] = useState<boolean>(false); // Redundant, inferred as 'boolean'

// ✅ Use type inference.
const USER_ROLE = 'admin'; // Inferred as narrowed string literal type 'admin'
const employees = new Map([['Gabriel', 32]]); // Inferred as 'Map<string, number>'
const [isActive, setIsActive] = useState(false); // Inferred as 'boolean'
データの不変性

不変性を重要な原則としてください。可能な限り、Readonly や ReadonlyArray などの型を使って、意図しない変更を防いでください。

  • Readonly 型は、意図しない副作用による誤った変更やバグを防ぐのに役立ちます。この保護は型チェック時に機能するものであり、実行時に値を凍結するわけではありません。
  • データを処理するときは、常に新しい配列、オブジェクト、その他の参照型データ構造を返してください。将来の開発者の認知負荷を抑えるため、データオブジェクトはフラットで簡潔に保つよう努めてください。
  • 変更は、複雑なオブジェクトを扱う場合やパフォーマンスを最適化する場合など、本当に必要なときに限って控えめに使用してください。
// ❌ Avoid data mutations
const removeFirstUser = (users: Array<User>) => {
  if (users.length === 0) {
    return users;
  }
  return users.splice(1);
};

// ✅ Use readonly type to prevent accidental mutations
const removeFirstUser = (users: ReadonlyArray<User>) => {
  if (users.length === 0) {
    return users;
  }
  return users.slice(1);
  // Using arr.splice(1) errors - Function 'splice' does not exist on 'users'
};
Satisfies を使った型安全な定数

as const satisfies 構文は、狭く readonly な推論と型検証を組み合わせます。定数の正確な値を保持しつつ、より広い型に準拠させたい場合に便利です。

主な利点:

  • as const による readonly 値
    • 定数が readonly として扱われるようにします。
    • 値の型をリテラル型に絞り、意図しない変更を防ぎます。
  • satisfies による検証
    • 推論された型を広げずに、オブジェクトがより広い型に準拠していることを確認します。
    • 絞り込まれた推論型を保ちながら、コンパイル時の型の不一致を検出しやすくします。

配列定数:

type UserRole = 'admin' | 'editor' | 'moderator' | 'viewer' | 'guest';

// ❌ Avoid constant of wide type
const DASHBOARD_ACCESS_ROLES: ReadonlyArray<UserRole> = ['admin', 'editor', 'moderator'];

// ❌ Avoid constant with incorrect values
const DASHBOARD_ACCESS_ROLES = ['admin', 'contributor', 'analyst'] as const;

// ✅ Use readonly constant of narrowed type
const DASHBOARD_ACCESS_ROLES = ['admin', 'editor', 'moderator'] as const satisfies ReadonlyArray<UserRole>;

オブジェクト定数:

type OrderStatus = {
  pending: 'pending' | 'idle';
  fulfilled: boolean;
  error: string;
};

// ❌ Avoid mutable constant of wide type
const IDLE_ORDER: OrderStatus = {
  pending: 'idle',
  fulfilled: true,
  error: 'Shipping Error',
};

// ❌ Avoid constant with incorrect values
const IDLE_ORDER = {
  pending: 'done',
  fulfilled: 'partially',
  error: 116,
} as const;

// ✅ Use readonly constant of narrowed type
const IDLE_ORDER = {
  pending: 'idle',
  fulfilled: true,
  error: 'Shipping Error',
} as const satisfies OrderStatus;
テンプレートリテラル型

テンプレートリテラル型を活用すると、値を補間して、正確で型安全な文字列構造を作成できます。幅広い文字列型を使う代わりとして強力であり、型安全性を高められます。

テンプレートリテラル型は、コンパイル時に TypeScript が把握している値を制約します。実行時に受け取る文字列は検証しません。

テンプレートリテラル型を採用すると、次のような利点があります。

  • タイプミスや無効な文字列によるエラーを防ぎます。
  • 型安全性とオートコンプリートのサポートを向上させます。
  • コードの保守性と可読性を高めます。

テンプレートリテラル型は、次のような実用的なさまざまな場面で役立ちます。

  • 文字列パターン - 型チェック時に特定の文字列パターンを適用するには、テンプレートリテラル型を使用します。

    // ❌ Avoid
    const appVersion = '2.6';
    // ✅ Use
    type Version = `v${number}.${number}.${number}`;
    const appVersion: Version = 'v2.6.1';
    
  • API エンドポイント - 有効な API ルートに値を制限するには、テンプレートリテラル型を使用します。

    // ❌ Avoid
    const userEndpoint = '/api/usersss'; // Type 'string' - Typo 'usersss': the route doesn't exist, leading to a runtime error.
    // ✅ Use
    type ApiRoute = 'users' | 'posts' | 'comments';
    type ApiEndpoint = `/api/${ApiRoute}`; // Type ApiEndpoint = "/api/users" | "/api/posts" | "/api/comments"
    const userEndpoint: ApiEndpoint = '/api/users';
    
  • 国際化キー - 翻訳キーに生の文字列を使うと、タイプミスや翻訳漏れにつながる可能性があります。テンプレートリテラル型を使用して、その構造を制約します。

    // ❌ Avoid
    const homeTitle = 'translation.homesss.title'; // Type 'string' - Typo 'homesss': the translation doesn't exist, leading to a runtime error.
    // ✅ Use
    type LocaleKeyPages = 'home' | 'about' | 'contact';
    type TranslationKey = `translation.${LocaleKeyPages}.${string}`; // Type TranslationKey = `translation.home.${string}` | `translation.about.${string}` | `translation.contact.${string}`
    const homeTitle: TranslationKey = 'translation.home.title';
    
  • CSS ユーティリティ - 色の値に生の文字列を使うと、無効な色や存在しない色につながる可能性があります。テンプレートリテラル型を使用して、既知の色名を適用し、カスタム値は # で始まるようにします。

    // ❌ Avoid
    const color = 'blue-450'; // Type 'string' - Color 'blue-450' doesn't exist, leading to a runtime error.
    // ✅ Use
    type BaseColor = 'blue' | 'red' | 'yellow' | 'gray';
    type Variant = 50 | 100 | 200 | 300 | 400;
    type Color = `${BaseColor}-${Variant}` | `#${string}`; // Type Color = "blue-50" | "blue-100" | "blue-200" ... | "red-50" | "red-100" ... | #${string}
    const iconColor: Color = 'blue-400';
    const customColor: Color = '#AD3128';
    
  • データベースクエリ - テーブル名や列名に生の文字列を使うと、タイプミスにつながる可能性があります。テンプレートリテラル型を使用して、有効なテーブルと列の組み合わせを定義します。

// ❌ Avoid
const query = 'SELECT name FROM usersss WHERE age > 30'; // Type 'string' - Typo 'usersss': table doesn't exist, leading to a runtime error.
// ✅ Use
type Table = 'users' | 'posts' | 'comments';
type Column<TTableName extends Table> =
  TTableName extends 'users' ? 'id' | 'name' | 'age' :
  TTableName extends 'posts' ? 'id' | 'title' | 'content' :
  TTableName extends 'comments' ? 'id' | 'postId' | 'text' :
  never;

type Query<TTableName extends Table> = `SELECT ${Column<TTableName>} FROM ${TTableName} WHERE ${string}`;
const userQuery: Query<'users'> = 'SELECT name FROM users WHERE age > 30'; // Accepted by Query<'users'>
const invalidQuery: Query<'users'> = 'SELECT title FROM users WHERE age > 30'; // Error: 'title' is not a column in 'users' table.
型 any と unknown

any 型は型チェックを迂回し、安全でない操作や代入を許可するため、使用してはいけません。これにより、重大なプログラミングエラーが見過ごされる可能性があります。

曖昧なデータを扱う場合は、any に対する型安全な代替手段である unknown を使用します。
unknown にはどのような値も代入できますが、プロパティにアクセスしたり、より具体的な型に代入したりする前に、型を絞り込む必要があります。

// ❌ Avoid any
const foo: any = 'five';
const bar: number = foo; // no type error

// ✅ Use unknown
const foo: unknown = 5;
const bar: number = foo; // type error - Type 'unknown' is not assignable to type 'number'

// Narrow the type before dereferencing it using:
// Type guard
const isNumber = (num: unknown): num is number => {
  return typeof num === 'number';
};
if (!isNumber(foo)) {
  throw Error(`API provided a fault value for field 'foo':${foo}. Should be a number!`);
}
const bar: number = foo;
型アサーションと非 null アサーション

型アサーション user as User と非 null アサーション user!.name は安全ではありません。どちらも TypeScript コンパイラーを黙らせるだけであり、実行時にアプリケーションがクラッシュするリスクを高めます。
サードパーティライブラリの型の不一致など、コードベースに導入する強い根拠がある場合に限り、例外として使用できます。

type User = { id: string; username: string; avatar: string | null };
// ❌ Avoid type assertions
const user = { name: 'Nika' } as User;

// ❌ Avoid non-nullability assertions
const getUsername = (user: User | null) => user!.username; // Runtime error when user is null
型エラー

TypeScript のエラーを回避できない場合は、最後の手段として @ts-expect-error を使用して抑制します。

このディレクティブは、抑制したエラーが存在しなくなった場合にコンパイラーへ通知し、古くなったエラーを再確認できるようにします。一方、@ts-ignore はエラーが解決した後も気づかれないまま残ることがあります。

  • @ts-expect-error を使用する場合は、必要な理由を説明する明確な記述を必ず付けてください。
  • 抑制したエラーを追跡しない @ts-ignore は避けてください。

{'@typescript-eslint/ban-ts-comment': [ 'error', { 'ts-expect-error': 'allow-with-description' }, ]}

// ❌ Avoid @ts-ignore as it will do nothing if the following line is error-free.
// @ts-ignore
const newUser = createUser('Gabriel');

// ✅ Use @ts-expect-error with description.
// @ts-expect-error: This library function has incorrect type definitions - createUser accepts string as an argument.
const newUser = createUser('Gabriel');
型定義

TypeScript では、型を定義する方法として type と interface の 2 つが用意されています。機能上の違いはいくつかありますが、ほとんどの場合、どちらも使用できます。一貫性を保つため、どちらか一方を選び、統一して使用してください。

<Rule prefix="すべての型を型エイリアスで定義する" href="https://typescript-eslint.io/rules/consistent-type-definitions"

{'@typescript-eslint/consistent-type-definitions': ['error', 'type']}

将来的にサードパーティの利用者が拡張する可能性のあるパッケージを開発する場合や、 チームがインターフェースを使った作業を好む場合は、interface の使用を検討してください。 このような場合は、必要に応じて lint ルールを無効にできます。たとえば、型のユニオンを 定義する場合です(例:`type Status = 'loading' | 'error'`)。
// ❌ Avoid interface definitions
interface UserRole = 'admin' | 'guest'; // Invalid - interfaces can't define type unions

interface UserInfo {
  name: string;
  role: 'admin' | 'guest';
}

// ✅ Use type definition
type UserRole = 'admin' | 'guest';

type UserInfo = {
  name: string;
  role: UserRole;
};

宣言マージを行う場合(例:サードパーティライブラリの型を拡張する場合)は、interface を使用し、必要に応じて lint ルールを無効にしてください。

// types.ts
declare namespace NodeJS {
  // eslint-disable-next-line @typescript-eslint/consistent-type-definitions
  export interface ProcessEnv {
    NODE_ENV: 'development' | 'production';
    PORT: string;
    CUSTOM_ENV_VAR: string;
  }
}

// server.ts
app.listen(process.env.PORT, () => {...}
配列型

<Rule prefix="配列型はジェネリック構文で定義してください" href="https://typescript-eslint.io/rules/array-type/#generic"

{'@typescript-eslint/array-type': ['error', { default: 'generic' }]}

'generic'と'array'の定義に機能上の違いはないため、チームが最も読みやすいと感じる方を選んでください。

// ❌ Avoid
const x: string[] = ['foo', 'bar'];
const y: readonly string[] = ['foo', 'bar'];

// ✅ Use
const x: Array<string> = ['foo', 'bar'];
const y: ReadonlyArray<string> = ['foo', 'bar'];
型のインポートとエクスポート

TypeScriptでは、エクスポートが実行時ではなく型システムにのみ存在することを示すために、インポートにtypeキーワードを指定できます。

型のインポートは必ず分離してください。

  • ツリーシェイキングとデッドコードの除去 — import typeはコンパイル時に消去されるため、バンドラーが解析または削除する実行時インポートは残りません。
  • 副作用の回避 — コンパイラーの設定によっては、型としてのみ使用される通常のインポートが、出力されるJavaScriptに残り、モジュールの副作用を実行する場合があります。
  • コードの明瞭さ — 実行時のインポートと型専用のインポートの違いを明確にできます。

{'@typescript-eslint/consistent-type-imports': 'error'}

// ❌ Avoid using `import` for both runtime and type
import { MyClass } from 'some-library';

// Even if MyClass is only used as a type, a regular import can pull the module into the runtime bundle.

// ✅ Use `import type`
import type { MyClass } from 'some-library';

// This import is removed from the emitted JavaScript.
サービスと型の生成

ドキュメントは書かれた瞬間から古くなります。誤ったドキュメントは、ドキュメントがないことよりも悪いものです。これは、API、メッセージングプロトコル、データベースなど、アプリがやり取りするモジュールを記述する型にも当てはまります。

REST、GraphQL、MQなどの外部サービスでは、Swagger、スキーマ、その他のソース(例:openapi-ts、graphql-config)を使用しているかどうかにかかわらず、契約から型を生成することが重要です。型を手作業で宣言して保守するのは避けてください。簡単に実際の仕様と食い違ってしまうためです。

生成された型を使うと、コンパイル時の契約を最新の状態に保てます。ただし、外部サービスから実行時に受信するデータの検証は行いません。

例外として、サービスのドキュメントがない、契約を取得するためにデータをフェッチできない、データベースにアクセスして型を推論できないなど、ほかに選択肢がない場合に限り、手作業で型を宣言してください。

コメント