mcpField
E
evolving-types

evolving-types

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

evolving-types について

代入に応じて推論型が変わる変数を扱う TypeScript 開発者向けのスキルです。進化する型の用途や脆弱さを解説し、明示的な型注釈で明確にする方法を示します。

主な機能

  1. 01進化する型の動作
  2. 02代入による型の絞り込み
  3. 03暗黙の any のリスク
  4. 04明示的な型注釈の指針
  5. 05配列の蓄積に対する関数型の代替

活用シーン

  1. 01未初期化変数の型付け
  2. 02空配列の型注釈の選択
  3. 03代入順に依存するコードの確認
  4. 04配列蓄積処理のリファクタリング

インストール

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

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill evolving-types

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

詳細

変化する型を理解する

概要

一部の変数は幅広い型で始まり、TypeScript が値の追加を認識するにつれて型が絞り込まれます。

これは、型は変化しないという規則の例外です。値を指定せずに初期化した変数や、空配列として初期化した変数は、代入した値に応じて絞り込まれる「変化する」型を持つことがあります。

このスキルを使う場面

  • null または undefined で初期化する変数
  • 空の状態から始まり、値が push される配列
  • any で始まり、型が絞り込まれる変数
  • 明示的な型注釈のほうが適している場面を理解する

絶対ルール

Evolving types work but are fragile.
Prefer explicit annotations for clarity.

覚えておいてください:

  • 型付きの初期値を持たない変数にのみ適用されます
  • 型は代入に応じて変化します
  • 最終的な型は、すべての代入が行われた後にのみ有効です
  • 明示的な型注釈のほうが分かりやすいことがよくあります

検出:変化する any

const result = [];  // any[]
result.push('a');   // string[]
result.push(1);     // (string | number)[]

result
// ^? (string | number)[]

型は push するたびに変化します。

変化する型の仕組み

未初期化の変数
let val;  // any (evolving)
val
// ^? let val: any

if (Math.random() < 0.5) {
  val = /hello/;
  val
  // ^? let val: RegExp
} else {
  val = 12;
  val
  // ^? let val: number
}
val
// ^? let val: number | RegExp

TypeScript は代入を追跡し、ユニオン型を計算します。

空配列
const arr = [];  // any[] (evolving)
arr.push(1);
arr
// ^? number[]

arr.push('hello');
arr
// ^? (string | number)[]
初期値が null または undefined
let x = null;  // any (evolving)
x
// ^? null

x = 12;
x
// ^? number

型の変化が止まるとき

変数がスコープを抜けるか、関数内で使われると、その型は固定されます。

function buildArray() {
  const arr = [];
  arr.push(1);
  arr.push(2);
  return arr;  // Type fixed as number[]
}

const myArray = buildArray();
myArray.push('hello');  // Error if return type is number[]

変化する型の問題点

順序が重要
const arr = [];
arr.push(1);
// arr is number[] here

// If this line is later:
arr.push('hello');
// arr is (string | number)[] but earlier uses assumed number[]
暗黙の any

noImplicitAny を指定すると、型注釈のない空配列は any[] になります。

const values = [];  // Implicit any[] - may cause lint warnings
不安定な型推論
let x = null;
x = 'hello';
x = 42;  // Now it's string | number

// Later, someone adds:
x = true;  // Now it's string | number | boolean

// All code using x must handle all possibilities

より良い方法:明示的な型注釈

// Clear intent, stable type
const result: number[] = [];
result.push(1);
result.push(2);
// result is always number[]

// Prevents accidents
result.push('hello');
//          ~~~~~~~
// Argument of type 'string' is not assignable to 'number'

変化する型を使ってもよい場合

短く単純なループ
const squares = [];
for (let i = 0; i < 5; i++) {
  squares.push(i * i);
}
// squares: number[] is clear from context
既知の型の蓄積
let result = null;
for (const item of items) {
  if (condition(item)) {
    result = item;
    break;
  }
}
// result evolves to ItemType | null

関数型の代替手段

配列の型を変化させる代わりに、関数型の構文を使う方法を優先してください。

// Don't:
const doubled = [];
for (const n of numbers) {
  doubled.push(n * 2);
}

// Do:
const doubled = numbers.map(n => n * 2);
// ^? number[]

型が直接推論されるため、変化させる必要はありません。

実例

// Evolving (works but fragile)
async function fetchData() {
  let data;
  try {
    const response = await fetch('/api');
    data = await response.json();
  } catch (e) {
    data = null;
  }
  return data;  // any
}

// Better: explicit
async function fetchData(): Promise<Data | null> {
  try {
    const response = await fetch('/api');
    return await response.json();
  } catch (e) {
    return null;
  }
}

プレッシャーに対抗するプロトコル

1. 「変化する型は機能する」

プレッシャー:「TypeScript が自動で判断してくれます」

**回答:**不安定で、新しいコードを追加すると予期せず変わることがあります。

**アクション:**安定性を高めるため、明示的な型注釈を追加します。

2. 「型がまだ分かりません」

プレッシャー:「型は実行時の条件によって変わります」

**回答:**可能な型は分かっているはずです。それらを宣言してください。

**アクション:**ユニオン型を使います:let x: string | number | null = null;

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

  • 型注釈のない const arr = []
  • 型注釈のない let x = null または let x
  • 代入の順序によって変わる型
  • リントで any[] の警告が出る

よくある正当化(すべて無効)

言い訳実際のところ
「TypeScript が推論してくれます」何かは推論しますが、望んだ型とは限りません
「型は後で追加します」その「後」は来ません。今追加してください
「一時的なコードです」一時的なコードは恒久的なものになります

クイックリファレンス

// EVOLVING (works but fragile)
const arr = [];       // any[]
arr.push(1);          // number[]
arr.push('a');        // (string | number)[]

// BETTER (stable and clear)
const arr: number[] = [];
arr.push(1);
arr.push('a');  // Error!

// BEST (functional)
const arr = items.map(item => item.value);
// Type inferred correctly

要点

変化する型は便利な機能ですが、ベストプラクティスではありません。

TypeScript は代入を通じて型を追跡できますが、明示的な型注釈のほうが分かりやすく、堅牢です。変化する型は、単純で局所的なコードに限って使ってください。それ以外の場合は、型注釈で意図を宣言してください。

参考資料

Dan Vanderkam 著「Effective TypeScript」、項目 25「変化する型を理解する」に基づいています。

コメント