typescript-types について
typescript-types は、現在のタスクに TypeScript の型規約を適用する Skill です。TypeScript コードを作成・レビューする開発者向けで、リポジトリ固有の規約を優先しつつ、型の絞り込み、不変性、不確かな値の安全な扱いを重視します。
主な機能
- 01リポジトリ規約に沿った型指針の適用
- 02型の絞り込みと推論の優先
- 03意図しない変更を防ぐ読み取り専用型
- 04テンプレートリテラル型による文字列制約
- 05曖昧な値への unknown の使用
- 06アサーションとエラー抑制の限定
活用シーン
- 01新しいコードに適した型表現の選択
- 02TypeScript の型安全性レビュー
- 03リポジトリ設定に合わせた型スタイルの調整
- 04サードパーティ型定義との不一致への対処
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ mkosir/ typescript-style-guide --skill typescript-typesインストール後、Claude Code で/typescript-typesを入力して読み込まれたか確認します。
詳細
型
現在のタスクのコンテキストで、TypeScript スタイルガイドの型に関する規約を適用してください。
ワークフロー
- 対象のリポジトリの規約と設定を確認します。
- 明示的なリポジトリの規約がある場合は、この方針に基づくガイダンスより優先します。
- タスクに関連するガイダンスのみを適用、確認、または説明します。
- 適切なモデルがコンテキストや判断に左右される場合は、重要なトレードオフを示します。
適用範囲
- 自動でチェックできる項目は、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"
将来的にサードパーティの利用者が拡張する可能性のあるパッケージを開発する場合や、 チームがインターフェースを使った作業を好む場合は、interface の使用を検討してください。 このような場合は、必要に応じて lint ルールを無効にできます。たとえば、型のユニオンを 定義する場合です(例:`type Status = 'loading' | 'error'`)。{
'@typescript-eslint/consistent-type-definitions': ['error', 'type']}
// ❌ 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)を使用しているかどうかにかかわらず、契約から型を生成することが重要です。型を手作業で宣言して保守するのは避けてください。簡単に実際の仕様と食い違ってしまうためです。
生成された型を使うと、コンパイル時の契約を最新の状態に保てます。ただし、外部サービスから実行時に受信するデータの検証は行いません。
例外として、サービスのドキュメントがない、契約を取得するためにデータをフェッチできない、データベースにアクセスして型を推論できないなど、ほかに選択肢がない場合に限り、手作業で型を宣言してください。