context-type-inference について
型推論に周囲のコンテキストがどう影響するかを扱う TypeScript スキルです。値を切り出した後も正確な型を保つため、型注釈、const アサーション、satisfies の使い方を説明します。
主な機能
- 01失われた型コンテキストの復元
- 02リテラル型の保持
- 03タプル型の精度維持
- 04satisfies による検証
活用シーン
- 01値の切り出し後に生じる型エラーの修正
- 02タプル型が配列型へ拡大するのを防止
- 03コンテキスト外へ移したコールバックの型付け
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ marius-townhouse/ effective-typescript-skills --skill context-type-inferenceインストール後、Claude Code で/context-type-inferenceを入力して読み込まれたか確認します。
詳細
型推論でコンテキストがどのように使われるかを理解する
概要
値をコンテキストから切り離すと、型エラーが発生することがあります。
TypeScript は、値とその値が使われる場所の両方に基づいて型を推論します。値を変数に取り出すとコンテキストが失われ、予想外の型エラーが発生することがあります。
このスキルを使う場面
- 値を定数に取り出すと型エラーが発生する
- コールバックのパラメーターの型が正しくない
- タプル型が配列型になる
- 文字列リテラル型が一般的な文字列型になる
鉄則
When extraction breaks types, restore context with:
annotations, const assertions, or satisfies.
覚えておきましょう:
- TypeScript は関数のパラメーターからコンテキストを使います
- 値を取り出すと、そのコンテキストが失われます
as constは型の拡大を防ぎますsatisfiesは検証を行いつつ、コンテキストを保持します
検出:コンテキストの喪失
type Language = 'JavaScript' | 'TypeScript' | 'Python';
function setLanguage(language: Language) { /* ... */ }
setLanguage('JavaScript'); // OK - context from parameter
let language = 'JavaScript'; // Inferred as string (widened)
setLanguage(language);
// ~~~~~~~~
// Argument of type 'string' is not assignable to 'Language'
値を取り出したときに、型が string に拡大されました。
解決策 1:型注釈
let language: Language = 'JavaScript';
setLanguage(language); // OK
型注釈によってコンテキストが与えられます。
解決策 2:const 宣言
const language = 'JavaScript';
// ^? const language: "JavaScript"
setLanguage(language); // OK
const 変数の型は、より狭い型(リテラル型)になります。
解決策 3:const アサーション
let language = 'JavaScript' as const;
// ^? let language: "JavaScript"
setLanguage(language); // OK
as const は、let を使う場合でも型の拡大を防ぎます。
タプル型:よくあるケース
function panTo(where: [number, number]) { /* ... */ }
panTo([10, 20]); // OK - inferred as tuple from context
const loc = [10, 20];
// ^? const loc: number[] (array, not tuple!)
panTo(loc);
// ~~~
// Argument of type 'number[]' is not assignable to '[number, number]'
型注釈で修正する
const loc: [number, number] = [10, 20];
panTo(loc); // OK
const アサーションで修正する
const loc = [10, 20] as const;
// ^? const loc: readonly [10, 20]
注意:as const を使うと readonly になります。関数が変更可能なタプルを期待する場合、これは使えません。
panTo(loc);
// ~~~
// 'readonly [10, 20]' is not assignable to '[number, number]'
入力を変更しない関数であれば、readonly [number, number] を受け取るようにする必要があります。
オブジェクトのプロパティ
type Point = [number, number];
const capitals1 = { ny: [-73.7562, 42.6526], ca: [-121.4944, 38.5816] };
// ^? { ny: number[]; ca: number[]; } (arrays, not tuples)
const capitals2 = {
ny: [-73.7562, 42.6526],
ca: [-121.4944, 38.5816],
} satisfies Record<string, Point>;
// ^? { ny: [number, number]; ca: [number, number]; } (tuples!)
satisfies は、正確な型を保持しながらコンテキストを与えます。
satisfies と型注釈の比較
const capitals3: Record<string, Point> = capitals2;
capitals3.pr; // No error! Property 'pr' returns Point (but undefined at runtime)
// ^? Point
capitals2.pr;
// ~~
// Property 'pr' does not exist
satisfies は正確なキーを保持しますが、型注釈では保持されません。
コールバックのコンテキスト
TypeScript は、コンテキストからコールバックのパラメーターの型を推論します。
// Types inferred from app.get declaration
app.get('/health', (request, response) => {
// ^? Request<...>
// ^? Response<...>
response.send('OK');
});
// Don't add redundant annotations:
app.get('/health', (request: express.Request, response: express.Response) => {
// Redundant and verbose
});
コールバックの取り出し
// Context lost when callback is extracted
const handler = (request, response) => {
// ^? any ^? any
response.send('OK');
};
// Fix: Add types
const handler: express.RequestHandler = (request, response) => {
// ^? Request ^? Response
response.send('OK');
};
オブジェクトのメソッド
const config = {
language: 'JavaScript' as const,
// ^? "JavaScript" (not string)
};
setLanguage(config.language); // OK
as const がない場合、config.language は string になります。
深い const アサーション
const obj = {
settings: {
language: 'JavaScript',
version: 4,
}
} as const;
// ^? { readonly settings: { readonly language: "JavaScript"; readonly version: 4; } }
as const は深く適用され、すべてがリテラル型を持つ読み取り専用になります。
圧力に負けないためのプロトコル
1. 「型アサーションを追加するだけ」
圧力:「as Language を使えば直せます」
回答: 型アサーションは安全性を迂回します。代わりに const または satisfies を使いましょう。
行動: リテラル型に絞り込むには as Type ではなく as const を使います。
2. 「インラインなら動いた」
圧力:「取り出すと、なぜ動かなくなるのですか?」
回答: コンテキストが失われています。明示的に復元しましょう。
行動: 型注釈を追加するか、const または satisfies を使います。
危険信号 — 立ち止まって再検討する
- 値を変数に取り出した後に型エラーが発生する
- コールバックのパラメーターが
any型になる - タプルが配列になる
- 文字列リテラル型が
stringになる
よくある正当化(すべて誤り)
| 言い訳 | 実際のところ |
|---|---|
| 「同じ値です」 | 値が同じでも、コンテキストが異なれば推論される型も異なります |
| 「TypeScript なら分かるはずです」 | 正確な型を推論するには、TypeScript にコンテキストが必要です |
「as を使って直します」 | 型アサーションではなく、as const または satisfies を使いましょう |
クイックリファレンス
type Lang = 'JS' | 'TS';
declare function setLang(l: Lang): void;
// DON'T: Lose context
let lang = 'JS'; // string
setLang(lang); // Error
// DO: Annotation
let lang: Lang = 'JS';
// DO: const
const lang = 'JS'; // "JS"
// DO: const assertion
let lang = 'JS' as const; // "JS"
// For objects: satisfies
const cfg = { lang: 'JS' } satisfies Record<string, Lang>;
要点
型推論ではコンテキストが重要です。
値を取り出すと、型のコンテキストが失われることがあります。型注釈、const 宣言、as const アサーション、または satisfies を使って復元しましょう。それぞれの状況に適した方法を理解してください。
参考資料
Dan Vanderkam 著「Effective TypeScript」の第 24 項「型推論でコンテキストがどのように使われるかを理解する」に基づいています。