mcpField
C
code-gen-independent

code-gen-independent

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

code-gen-independent について

型チェックと JavaScript 生成が別の処理であり、型が実行時には消去されることを解説する TypeScript スキルです。開発者が型宣言に頼らず、実際の実行時チェックや変換を選ぶために役立ちます。

主な機能

  1. 01型チェックとコード生成の区別
  2. 02実行時の型消去の説明
  3. 03プロパティやタグによる検査例
  4. 04型アサーションと変換の違い
  5. 05型によるオーバーロードの問題

活用シーン

  1. 01型エラーがあっても JavaScript が生成される理由の調査
  2. 02インターフェースの実行時検査方法の選択
  3. 03型アサーションに頼らない値の変換

インストール

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

$npx skills add https://github.com/marius-townhouse/effective-typescript-skills --skill code-gen-independent

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

詳細

コード生成は型とは独立していることを理解する

概要

TypeScript のコンパイルと型チェックは別々の処理です。

TypeScript コンパイラーは、(1)TypeScript を JavaScript にトランスパイルすることと、(2)型をチェックすることの 2 つを行います。これらは独立しており、型エラーがあってもコード生成は妨げられず、型は実行時には存在しません。

このスキルを使う場面

  • 型エラーがあるのにコードが実行される理由が分からない
  • 実行時に型をチェックしようとしている
  • インターフェースに instanceof を使っている
  • 型が実行時の動作に影響すると期待している

絶対に守るべきルール

NEVER expect TypeScript types to exist at runtime. They are erased.

重要な事実:

  • 型エラーがあるコードでも、JavaScript の出力を生成できます
  • 型はコンパイル時に消去されます
  • TypeScript のインターフェースに instanceof は使えません
  • 実行時のチェックには JavaScript の構文が必要です

型エラーが出力を妨げることはありません

$ cat test.ts
let x = 'hello';
x = 1234;  // Type error!

$ tsc test.ts
test.ts:2:1 - error TS2322: Type 'number' is not assignable to type 'string'

$ cat test.js
var x = 'hello';
x = 1234;  // JavaScript was still generated!

型エラーは警告のようなもので、コンパイルを停止させません。

実行時に型をチェックすることはできません

interface Square {
  width: number;
}
interface Rectangle extends Square {
  height: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape instanceof Rectangle) {
      //            ~~~~~~~~~ 'Rectangle' only refers to a type,
      //                      but is being used as a value here
    return shape.width * shape.height;
  }
  return shape.width * shape.width;
}

なぜでしょうか? interface と type は消去されます。JavaScript には存在しません。

実行時の型チェック方法

方法 1:プロパティのチェック
function calculateArea(shape: Shape) {
  if ('height' in shape) {
    // TypeScript knows shape is Rectangle here
    return shape.width * shape.height;
  }
  return shape.width * shape.width;
}
方法 2:タグ付きユニオン(推奨)
interface Square {
  kind: 'square';
  width: number;
}
interface Rectangle {
  kind: 'rectangle';
  width: number;
  height: number;
}
type Shape = Square | Rectangle;

function calculateArea(shape: Shape) {
  if (shape.kind === 'rectangle') {
    return shape.width * shape.height;
  }
  return shape.width * shape.width;
}
方法 3:クラスを使う
class Square {
  constructor(public width: number) {}
}
class Rectangle extends Square {
  constructor(width: number, public height: number) {
    super(width);
  }
}

function calculateArea(shape: Square | Rectangle) {
  if (shape instanceof Rectangle) {
    // This works! Classes exist at runtime
    return shape.width * shape.height;
  }
  return shape.width * shape.width;
}

型操作は実行時に影響しません

function asNumber(val: number | string): number {
  return val as number;  // Type assertion
}

// Generated JavaScript:
function asNumber(val) {
  return val;  // No conversion! Just returns val as-is
}

型アサーションは値を変換しません。実行時のコードを使ってください。

function asNumber(val: number | string): number {
  return typeof val === 'string' ? Number(val) : val;
}

実行時の型は宣言された型と異なる場合があります

function setLightSwitch(value: boolean) {
  switch (value) {
    case true: turnLightOn(); break;
    case false: turnLightOff(); break;
    default:
      console.log("I'm afraid I can't do that.");
      // TypeScript thinks this is unreachable, but...
  }
}

// At runtime, someone could call:
setLightSwitch("ON" as any);  // Hits the default case!

API レスポンス、外部データ、any 型により、実行時の型の不一致が起こることがあります。

型に基づくオーバーロードはできません

// ❌ This doesn't work - types are erased
function add(a: number, b: number) { return a + b; }
function add(a: string, b: string) { return a + b; }
// ~~~ Duplicate function implementation

// ✅ Use a single implementation with union type
function add(a: number | string, b: number | string) {
  if (typeof a === 'number' && typeof b === 'number') {
    return a + b;
  }
  return String(a) + String(b);
}

圧力に負けないためのプロトコル

1. 「as Type を使えばいいだけ」

圧力:「型アサーションで値を変換できます」

**回答:**型アサーションは値を変換しません。コンパイル時にのみ有効です。

**対応:**実際に実行時に変換するコードを書いてください。

2. 「instanceof を使う」

圧力:「instanceof は自分のインターフェースでも動くはずです」

**回答:**インターフェースは実行時には存在しません。タグ付きユニオンかクラスを使ってください。

**対応:**判別用プロパティを追加するか、クラスを使ってください。

危険な兆候 ― 立ち止まって再考してください

  • インターフェースや型に instanceof を使う
  • 型アサーションで値が変換されると期待する
  • 型エラーが JavaScript の出力を妨げると思い込む
  • 実行時の検証を TypeScript の型に頼る

クイックリファレンス

TypeScript の構文実行時に存在するか
interfaceいいえ
typeいいえ
型注釈(: T)いいえ
型アサーション(as T)いいえ
classはい
enum(const なし)はい
タグ付きユニオンのプロパティはい

要点

型は消去されます。コード生成は型チェックとは独立しています。

TypeScript の型が存在するのはコンパイル時だけです。実行時の型チェックには、プロパティのチェック、タグ付きユニオン、クラス、または typeof/instanceof(型ではなく値に対して)といった JavaScript の構文を使ってください。

参考資料

Dan Vanderkam 著『Effective TypeScript』項目 3「コード生成は型とは独立していることを理解する」に基づいています。

コメント