typescript について
TypeScript 5+ 向けのコーディング規約をまとめたスキルです。型によるドメイン表現や状態の明示的な処理、実用的なコンパイラー設定を通じて、開発者の実装と変更を支援します。
主な機能
- 01ブランド型とユニオン型でドメインを表現
- 02型アサーションより satisfies を優先
- 03enum の代わりに const オブジェクトを使用
- 04unknown を絞り込み any を隔離
- 05型専用インポートを分離
- 06厳格なコンパイラーチェックを設定
活用シーン
- 01新しい TypeScript コードへの規約適用
- 02変更箇所の型安全性の確認
- 03TypeScript コンパイラーの基本設定
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ alexander-danilenko/ cortex-ai-skills --skill typescriptインストール後、Claude Code で/typescriptを入力して読み込まれたか確認します。
詳細
TypeScript
TypeScript 5 以降のプロジェクト共通の規約です。新しく記述するコードや変更するコードに適用してください。依頼がない限り、変更していないファイルを規約に合わせてリファクタリングしないでください。
規約
- チェックではなく、型でドメインをモデル化します。 ブランド型(
type UserId = string & { readonly __brand: 'UserId' })や判別可能なユニオンを使うと、エラーをコンパイル時に検出でき、コストもかかりません。実行時ガードでは、同じバグを本番環境で検出することになります。 - 状態を持つものには判別可能なユニオンを使います — リクエストのライフサイクル、フォームの状態、解析結果などです。これにより、コンパイラーがすべての状態の処理を強制するため、新しいバリアントを追加すると、暗黙のフォールスルーではなくエラーとして表面化します。
asよりsatisfiesを使います。satisfiesは狭く推論されたリテラル型を保ちながら型との適合性を検証しますが、asはチェックを無効にします。asを使うのは、コンパイラーが把握できないことを確信している場合に限り、理由をコメントに記載してください。enumより定数オブジェクトを使います。as constオブジェクトは、コードが出力されず、名目型に関する予期しない動作もない通常の値です。一方、enumは実行時コードを生成し、const enumはisolatedModulesで問題を起こします。anyはバグの報告です。unknownを使い、型を絞り込んでください。境界でanyが本当に避けられない場合は、呼び出し箇所へ広がらないよう、1 つのアダプター関数内に隔離してください。- 型専用インポートを分けます(
import type { … })。バンドラーは型専用インポートを問題なく消去します。通常のインポートと混在させると、削除したかった実行時依存関係が残ることがあります。 - 型推論を活用します。 エクスポートするシグネチャやパブリックなシグネチャには型注釈を付け、内部のものは型推論に任せてください。注釈の付けすぎはノイズになり、実際の型から乖離していきます。
- ライブラリーとして利用されるものには
.d.tsを提供します(declaration: true)。これがないと、利用者側ではanyにフォールバックし、型付けの成果がパッケージの境界で止まってしまいます。 - モノレポでは、プロジェクト参照と
compositeを使います。 インクリメンタルビルドでは変更されたものだけを再ビルドします。単一のフラットなtsconfigでは、編集のたびにすべてが再コンパイルされます。
押さえておきたい 2 つのイディオム
網羅性は、強制しなければ実際には保証されません。ユニオンに対する switch は、新しいバリアントが追加されてもコンパイルに成功します。assertNever を使うことで、更新が必要なすべての箇所でビルドエラーになるようにできます。
function assertNever(x: never): never {
throw new Error(`Unhandled variant: ${JSON.stringify(x)}`);
}
function area(s: Shape): number {
switch (s.kind) {
case "circle":
return Math.PI * s.r ** 2;
case "rect":
return s.w * s.h;
default:
return assertNever(s); // add a variant → compile error here
}
}
ブランド型は、それを取得する唯一の方法が検証を行う関数である場合にのみ、導入する価値があります。
type Brand<T, B> = T & { readonly __brand: B };
type Email = Brand<string, "Email">;
export function toEmail(raw: string): Email {
if (!raw.includes("@")) throw new Error(`Not an email: ${raw}`);
return raw as Email;
}
コンストラクター内の as は、唯一正当な使い方です。未検証の値を検証済みの値に変換する境界だからです。それ以外の場所で Email にキャストすると、この型を導入した意味がなくなります。
tsconfig の基本設定
strict: true で有効になるのは、必要な機能のすべてではありません。デフォルトの設定では見逃される種類のバグをそれぞれ検出できるため、以下を追加してください。
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"noPropertyAccessFromIndexSignature": true,
"noImplicitOverride": true,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"isolatedModules": true,
"declaration": true,
"skipLibCheck": true
}
}
noUncheckedIndexedAccess は最も効果が高い設定であり、導入時の影響も最も大きくなります。これを有効にすると、arr[i] の戻り値は実態どおり T | undefined になります。新しいプロジェクトでは有効にし、既存のプロジェクトでは移行が必要になることを想定してください。
モジュール解決には、Node が出力を実行する場合は NodeNext を、バンドラーが実行する場合は bundler を使います。