precise-string-types について
文字列の有効値が限られる場合に、より厳密な型を使うよう促す TypeScript 向けコーディングスキルです。リテラルユニオン、keyof、テンプレートリテラル型を扱い、安全で明確な API 設計に役立ちます。
主な機能
- 01文字列リテラルユニオンの指針
- 02オブジェクトキーに keyof を使用
- 03パターンにテンプレートリテラル型
- 04型安全な API 引数の提案
- 05文字列型の移行手順
活用シーン
- 01引数を有効値に限定
- 02関数引数をオブジェクトキーに限定
- 03定義済みパターンの文字列に型を付与
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill precise-string-typesインストール後、Claude Code で/precise-string-typesを入力して読み込まれたか確認します。
詳細
string 型よりも、より正確な代替型を優先する
概要
string 型は範囲が広大です。可能な限り、より狭い型を使用してください。
文字列リテラル型のユニオン、テンプレートリテラル型、keyof を使うと、単なる string よりも優れた型安全性、オートコンプリート、ドキュメントを得られます。
このスキルを使う場面
- 有効な値が限られたプロパティを定義するとき
- 特定の文字列を受け取る関数パラメーターを作成するとき
- オブジェクトのキーやプロパティ名を扱うとき
- 「文字列型に依存しすぎた」コードを見かけたとき
- 文字列パラメーターでオートコンプリートが表示されないとき
絶対のルール
NEVER use plain string when the valid values are limited.
例外はありません:
- 「あとで値が増えるかもしれない」場合でも
- 「そのほうが簡単だから」という場合でも
- 「enum は大げさすぎる」という場合でも
検出:「文字列型に依存しすぎた」コードの兆候
文字列が特定の値しか取り得ないなら、それを明示してください。
// ❌ VIOLATION: Too broad - accepts any string
interface Album {
artist: string;
title: string;
releaseDate: string; // What format? Any string works!
recordingType: string; // "live" or "studio"... but also "banana"
}
// These are all valid but wrong:
const album: Album = {
artist: 'Miles Davis',
title: 'Kind of Blue',
releaseDate: 'August 17th, 1959', // Wrong format
recordingType: 'Studio', // Wrong case
};
解決策:文字列リテラル型のユニオン
// ✅ CORRECT: Precise types
type RecordingType = 'studio' | 'live';
interface Album {
artist: string;
title: string;
releaseDate: Date; // Use Date, not string
recordingType: RecordingType;
}
const album: Album = {
artist: 'Miles Davis',
title: 'Kind of Blue',
releaseDate: new Date('1959-08-17'),
recordingType: 'Studio',
// ~~~~~~~~ Type '"Studio"' is not assignable to type 'RecordingType'
};
正確な型のメリット
1. タイプミスを検出できる
type Direction = 'north' | 'south' | 'east' | 'west';
function move(direction: Direction) { /* ... */ }
move('nroth'); // Error: Did you mean 'north'?
2. オートコンプリートが表示される
// With string: no suggestions
function move(direction: string) { }
move('|') // Nothing
// With union: suggestions appear
function move(direction: Direction) { }
move('|') // Suggests: north, south, east, west
3. 有効な値を明示できる
/** What environment was this recording made in? */
type RecordingType = 'live' | 'studio';
// The type IS the documentation
function getAlbums(type: RecordingType) { }
// IDE shows: (parameter) type: "live" | "studio"
オブジェクトのキーに keyof を使う
// ❌ BAD: Any string is accepted
function pluck(records: any[], key: string): any[] {
return records.map(r => r[key]);
}
// ✅ GOOD: Only valid keys accepted
function pluck<T>(records: T[], key: keyof T): T[keyof T][] {
return records.map(r => r[key]);
}
const albums: Album[] = [/* ... */];
pluck(albums, 'artist'); // OK, returns string[]
pluck(albums, 'artits'); // Error: Did you mean 'artist'?
さらに優れた方法:ジェネリックなキーのパラメーター
function pluck<T, K extends keyof T>(records: T[], key: K): T[K][] {
return records.map(r => r[key]);
}
const dates = pluck(albums, 'releaseDate');
// ^? const dates: Date[] // Precise return type!
const artists = pluck(albums, 'artist');
// ^? const artists: string[]
パターンにはテンプレートリテラル型を使う
// Match strings like "GET /users" or "POST /items"
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiRoute = `${HttpMethod} /${string}`;
function handleRoute(route: ApiRoute) { }
handleRoute('GET /users'); // OK
handleRoute('POST /items'); // OK
handleRoute('PATCH /users'); // Error: not a valid HttpMethod
handleRoute('GET users'); // Error: missing /
string が適切な場合
interface Album {
// These genuinely can be any string:
artist: string; // "Miles Davis", "Queen", etc.
title: string; // Unlimited possibilities
// These should NOT be string:
recordingType: RecordingType; // Limited options
genre: Genre; // Limited options
}
圧力に負けないためのプロトコル
1. 「値が増えるかもしれない」
圧力:「あとで録画タイプを追加したらどうするのですか?」
回答: ユニオンに追加してください。TypeScript が、対応する必要のある箇所をすべて知らせてくれます。
対応: まず既知の値から始めてください。必要になったら拡張します。
2. 「ユーザーは何でも入力できる」
圧力:「ユーザー入力にはどんな文字列も含まれる可能性があります」
回答: 境界で検証してください。内部では正確な型を使います。
対応: ユーザー入力を解析・検証してから、コード内では正確な型を使ってください。
3. 「enum は大げさすぎる」
圧力:「これに enum を作りたくありません」
回答: 文字列リテラル型のユニオンは enum よりも軽量で、同じように正確です。
対応: enum の代わりに type X = 'a' | 'b' | 'c' を使ってください。
危険信号 — 作業を止めて再検討する
- 有効な文字列値を説明するコメントがある
- 文字列パラメーターに対する実行時検証がある
- 文字列を扱う switch 文に「ここには到達しないはず」という default 分岐がある
- オートコンプリートが表示されない文字列パラメーターがある
- 文字列値のタイプミスによるバグがある
よくある正当化(すべて誤り)
| 言い訳 | 実際のところ |
|---|---|
| 「ただの文字列です」 | 限られた集合から選ばれる文字列です。そのことを表現してください。 |
| 「あとで検証します」 | 境界で検証し、内部では型を使ってください。 |
| 「値が多すぎます」 | 有限なら列挙してください。TypeScript が対応します。 |
| 「enum は見た目が悪い」 | 代わりに文字列リテラル型のユニオンを使ってください。 |
クイックリファレンス
| 現在の型 | 代わりに使うもの |
|---|---|
有効な値が N 個ある string | 'value1' | 'value2' | ... |
| オブジェクトのキーを受け取るパラメーター | keyof T |
| パターンに従う文字列 | テンプレートリテラル型 |
| 文字列としての日時 | Date |
| 文字列としての URL | ブランド型を検討する |
移行パターン
// Step 1: Find strings with limited values
interface Config {
environment: string; // "dev", "staging", "prod"
}
// Step 2: Create the union type
type Environment = 'development' | 'staging' | 'production';
// Step 3: Use it
interface Config {
environment: Environment;
}
// Step 4: Fix all the errors (that's the point!)
const config: Config = { environment: 'dev' };
// ~~~~~ Did you mean 'development'?
まとめ
string は文字列型における any です。
値が限られているなら、その制約を表現してください。有限の集合には文字列リテラル型のユニオン、オブジェクトのキーには keyof、パターンにはテンプレートリテラル型を使いましょう。コードの安全性が高まり、IDE がより役立つようになります。
参考文献
Dan Vanderkam 著『Effective TypeScript』項目 35「Prefer More Precise Alternatives to String Types」に基づいています。