mcpField
U
understand-type-widening

understand-type-widening

github.com/marius-townhouse開発645 回インストール38 週間前に更新
リポジトリ

understand-type-widening について

宣言方法が型推論に与える影響を理解したい TypeScript 開発者向けのスキルです。プリミティブ、オブジェクト、配列、リテラルの型の拡大と、その制御方法を解説します。

主な機能

  1. 01let と const による型の違いを解説
  2. 02オブジェクトのプロパティと配列の拡大を説明
  3. 03as const でリテラル型を保持
  4. 04注釈と satisfies による推論の制御
  5. 05リテラル型とタプルのエラーを診断

活用シーン

  1. 01文字列リテラルが string になる理由の確認
  2. 02広すぎるオブジェクト型や配列型への対処
  3. 03設定値をリテラル型のユニオンに適合
  4. 04タプルが通常の配列になる問題の防止

インストール

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

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill understand-type-widening

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

詳細

型の拡大を理解する

概要

TypeScript は値から型を推論する際、型を拡大することがよくあります。

"x" で初期化された変数は、任意の文字列を保持するつもりかもしれませんし、リテラル型の "x" だけを保持するつもりかもしれません。TypeScript はヒューリスティックに基づいて推測します。これを理解すると、予測しやすいコードを書けるようになります。

このスキルを使う場面

  • 型が "specific-value" ではなく string になる理由がわからない
  • 同じ値でも const と let で型が異なる
  • 配列リテラルの要素型が予想と異なる
  • オブジェクトのプロパティ型が予想より広い
  • リテラル型を代入できないという型エラーが発生する

鉄則

ALWAYS understand how your declaration style affects inferred types.

覚えておきましょう:

  • let 変数ではリテラル型が基本型に拡大されます
  • const 変数ではリテラル型が保持されます(プリミティブ型の場合)
  • const を使っても、オブジェクトや配列の中身は拡大されます
  • リテラル型を完全に推論させるには as const を使います

検出:予想外の型拡大

TypeScript が予想より広い型を推論している場合、型が拡大されています。

// Primitive widening with let
let x = 'x';
//  ^? let x: string  (not "x")

// No widening with const (for primitives)
const y = 'y';
//    ^? const y: "y"

// But object properties still widen
const obj = { x: 1, y: 2 };
//    ^? const obj: { x: number; y: number }  (not { x: 1, y: 2 })

型の拡大が存在する理由

TypeScript は、次の2つの目標のバランスを取る必要があります。

  1. 具体性 - 狭い型を使って実際のバグを検出する
  2. 柔軟性 - 妥当な変更を許容する
// Without widening, this would fail:
let x = 'x';  // If inferred as "x", then...
x = 'y';      // Error! "y" is not assignable to "x"

// With widening:
let x = 'x';  // Inferred as string
x = 'y';      // OK

型の拡大ルール

ルール1:let では拡大され、const では保持される(プリミティブ型)
// let → widened
let a = 'hello';
//  ^? let a: string

let b = 42;
//  ^? let b: number

let c = true;
//  ^? let c: boolean

// const → literal
const d = 'hello';
//    ^? const d: "hello"

const e = 42;
//    ^? const e: 42

const f = true;
//    ^? const f: true
ルール2:オブジェクトのプロパティは常に拡大される
const point = { x: 3, y: 4 };
//    ^? const point: { x: number; y: number }

// Not { x: 3, y: 4 } - because you might do:
point.x = 10;  // This must be valid
ルール3:配列の要素は拡大される
const arr = [1, 2, 3];
//    ^? const arr: number[]

// Not [1, 2, 3] or readonly [1, 2, 3]
ルール4:要素の型が混在する配列はユニオン型になる
const mixed = [1, 'x'];
//    ^? const mixed: (string | number)[]

// TypeScript picks the "best common type"

型の拡大を制御する

リテラル型を完全に推論させるには as const を使う
// Regular object - properties widen
const obj1 = { x: 1, y: 2 };
//    ^? const obj1: { x: number; y: number }

// With as const - properties are literal and readonly
const obj2 = { x: 1, y: 2 } as const;
//    ^? const obj2: { readonly x: 1; readonly y: 2 }

// Arrays too
const arr = [1, 2, 3] as const;
//    ^? const arr: readonly [1, 2, 3]
型注釈を使って明示する
// Annotate to get the type you want
const x: 'x' | 'y' = 'x';
//    ^? const x: "x" | "y"

// Can be reassigned to 'y', but nothing else
型チェック付きの推論には satisfies を使う
type Point = { x: number; y: number };

// Type annotation: loses literal types
const p1: Point = { x: 1, y: 2 };
//    ^? const p1: Point

// satisfies: keeps literals while checking structure
const p2 = { x: 1, y: 2 } satisfies Point;
//    ^? const p2: { x: number; y: number }

p2.x;  // number (not 1, but still good for inference)

よくある型の拡大に関する問題

問題:リテラル型が必要なのに、string が得られる
type HTTPMethod = 'GET' | 'POST' | 'PUT';

function makeRequest(method: HTTPMethod) { /* ... */ }

let method = 'GET';
makeRequest(method);
//          ~~~~~~ Argument of type 'string' is not assignable

解決策:

// Solution 1: Use const
const method = 'GET';
makeRequest(method);  // OK

// Solution 2: Type annotation
let method: HTTPMethod = 'GET';
makeRequest(method);  // OK

// Solution 3: as const
let method = 'GET' as const;
makeRequest(method);  // OK
問題:オブジェクトのプロパティ型が広すぎる
type Config = {
  mode: 'development' | 'production';
  debug: boolean;
};

function configure(config: Config) { /* ... */ }

const config = { mode: 'development', debug: true };
configure(config);
//        ~~~~~~ Type 'string' is not assignable to type '"development" | "production"'

解決策:

// Solution 1: Type annotation
const config: Config = { mode: 'development', debug: true };

// Solution 2: as const on the whole object
const config = { mode: 'development', debug: true } as const;

// Solution 3: as const on just the property
const config = { mode: 'development' as const, debug: true };
問題:タプルが配列になる
function setPoint(point: [number, number]) { /* ... */ }

const coords = [10, 20];
setPoint(coords);
//       ~~~~~~ Type 'number[]' is not assignable to type '[number, number]'

解決策:

// Solution 1: Type annotation
const coords: [number, number] = [10, 20];

// Solution 2: as const (makes it readonly)
const coords = [10, 20] as const;
// Note: readonly [10, 20] may not be assignable to [number, number]
// depending on the function's type

圧力に屈しないための手順

1. 「とにかく any を使う」

圧力:「型がおかしいなら、とにかく any にキャストすればいい」

**回答:**型は正しく、望むより広いだけです。

対応:const、型注釈、または as const を使います。

2. 「TypeScript はばかげている」

圧力:「明らかに 'GET' なのに、なぜ string と推論するのか」

**回答:**TypeScript は let 変数の値が変わると想定しています。

**対応:**変更されない値には const を使います。

危険信号 — 作業を止めて再検討する

  • リテラル型のエラーをキャストで解決しようとしている
  • オブジェクトのプロパティが "specific" ではなく string になることに驚いた
  • タプル型が配列になっている
  • ユニオンのリテラル型が予期せず拡大される

よくある正当化(すべて誤り)

言い訳実際のところ
「const ならリテラル型になるはず」プリミティブ型に限られ、オブジェクトの中身には当てはまりません
「値は明らかに X だ」let は値が変わる可能性を意味します
「as any で解決できる」適切な型の絞り込み方法を使いましょう

クイックリファレンス

宣言値推論される型
let x = "hello"文字列リテラルstring
const x = "hello"文字列リテラル"hello"
const x = { a: 1 }オブジェクト{ a: number }
const x = { a: 1 } as constオブジェクト{ readonly a: 1 }
const x = [1, 2]配列number[]
const x = [1, 2] as const配列readonly [1, 2]

要点

TypeScript は妥当な変更を許容するために型を拡大します。

プリミティブ型には const を使い、型を明示するには型注釈を使い、リテラル型を完全に推論させる必要がある場合は as const を使いましょう。型の拡大を理解すると、予想外の型エラーを防ぎ、より正確な型を書けるようになります。

参考資料

Dan Vanderkam 著『Effective TypeScript』項目20「変数の型がどのように決まるかを理解する」に基づいています。

コメント