【TypeScript】TS2322の原因と解決方法|Type is not assignable to typeを完全解説

【TypeScript】TS2322の原因と解決方法|Type is not assignable to typeを完全解説 TypeScript

TypeScriptで開発をしていると、最も頻繁に遭遇するエラーの一つが TS2322 です。ターミナルやエディタに突然表示される Type 'X' is not assignable to type 'Y' というメッセージに、手が止まってしまった経験はありませんか?

TS2322は「型の代入互換性エラー」を意味し、変数・プロパティ・戻り値などに期待される型と異なる型の値を代入しようとしたときに発生します。TypeScriptの型システムの根幹をなすエラーであり、基本型(string / number)の単純な不一致から、ジェネリクスやReactコンポーネントのProps、外部ライブラリとの型の食い違いまで、ありとあらゆる場面で発生します。

この記事では、TS2322エラーをパターン別に徹底分類し、それぞれについて実際のエラーメッセージNGコード原因の解説OKコード(修正例)を掲載します。エラーが出たらこの記事を辞書的に参照すれば、すぐに解決できるはずです。

この記事で学べること

  • TS2322エラーメッセージの読み方意味
  • 基本型(string / number / boolean)での型不一致の原因と修正
  • リテラル型と widening による TS2322
  • オブジェクト型でのプロパティ不一致・余剰プロパティチェック
  • 配列・タプル型での型不一致
  • ユニオン型・交差型での TS2322
  • 関数の戻り値・コールバック・Promise での型不一致
  • ジェネリクスでの型引数エラー
  • React / JSXでの Props・イベント・Ref の型不一致
  • 外部ライブラリとの型不一致の対処法
  • アンチパターン(as / any / @ts-ignore)と正しい解決法
  • 実務でよくあるTS2322パターン10選

前提知識:この記事はTypeScriptの基本的な型の書き方を理解している方を対象としています。型の基本から学びたい方は【TypeScript】型の書き方 完全入門を先にお読みください。

スポンサーリンク
  1. TS2322エラーとは?
    1. エラーメッセージの読み方
    2. なぜTS2322エラーが発生するのか
  2. 基本的なケースと解決方法
    1. string を number に代入しようとした場合
    2. number を string に代入しようとした場合
    3. boolean への不正な代入
    4. リテラル型への代入エラー
    5. null / undefined の代入エラー
    6. 数値リテラル型の不一致
  3. オブジェクト型でのTS2322
    1. プロパティの型不一致
    2. 必須プロパティの欠如
    3. 余剰プロパティチェック(Excess Property Checks)
    4. オプショナルプロパティ関連のTS2322
    5. ネストしたオブジェクトの型不一致
    6. インターフェースの実装ミス(クラス)
    7. 型エイリアスとインターフェースの構造不一致
  4. 配列・タプルでのTS2322
    1. 配列要素型の不一致
    2. readonly 配列への代入
    3. タプル型の要素数・型の不一致
    4. 配列をタプルに代入する場合
  5. ユニオン型・リテラル型でのTS2322
    1. ユニオン型の絞り込み不足
    2. リテラル型の widening(型の拡大)
    3. as const の使い方と注意点
    4. Discriminated Union(判別ユニオン)での不一致
    5. ユニオン型の網羅性チェック(Exhaustive Check)
    6. 交差型(Intersection Type)での不一致
  6. 関数の戻り値でのTS2322
    1. 戻り値の型が合わない
    2. Promise / async 関数での型不一致
    3. コールバック関数の型不一致
    4. void vs undefined の違い
    5. アロー関数の暗黙的な return
  7. ジェネリクスでのTS2322
    1. 型引数の制約違反
    2. ジェネリクスの型推論の失敗
    3. 条件型(Conditional Types)での不一致
    4. ジェネリクスとユーティリティ型の組み合わせ
  8. React / JSX でのTS2322
    1. Props の型不一致
    2. イベントハンドラの型不一致
    3. children の型不一致
    4. Ref の型不一致
    5. コンポーネントの戻り値型
    6. useState の型不一致
  9. 外部ライブラリとのTS2322
    1. @types パッケージのバージョン不一致
    2. 型定義がないライブラリ
  10. アンチパターンと正しい解決法
    1. as による型アサーションの危険性
    2. any で逃げるリスク
    3. @ts-ignore / @ts-expect-error は最終手段
  11. 実務でよくあるTS2322パターン10選
    1. パターン1: APIレスポンスの型不一致
    2. パターン2: 環境変数(string | undefined → string)
    3. パターン3: Date型関連
    4. パターン4: enum 関連
    5. パターン5: null と undefined の混同
    6. パターン6: Map / Set の型
    7. パターン7: JSON.parse の戻り値
    8. パターン8: Object.keys / Object.entries の戻り値型
    9. パターン9: Promise.all / Promise.race の型
    10. パターン10: フォーム値のパース(HTMLInputElement.value は常に string)
  12. まとめ
    1. TS2322 vs 関連エラーコードの違い
    2. 関連記事

TS2322エラーとは?

TS2322 は TypeScript コンパイラが出すエラーの中で最も遭遇頻度が高いエラーコードです。正式なエラーメッセージは次のとおりです。

error TS2322: Type ‘X’ is not assignable to type ‘Y’.

日本語に訳すと「型 ‘X’ は型 ‘Y’ に代入できません」となります。つまり、ある変数やプロパティに対して期待される型(Y)と、実際に代入しようとしている値の型(X)が一致しない場合に発生するエラーです。

エラーメッセージの読み方

TS2322のエラーメッセージは、一定のフォーマットに従っています。具体例で構造を確認しましょう。

エラーメッセージの例
src/index.ts:3:7 - error TS2322: Type 'string' is not assignable to type 'number'.

3   const age: number = "25";
                    ~~~

このエラーメッセージは以下のパーツで構成されています。

パーツ 意味
ファイルパス src/index.ts エラーが発生したファイル
行:列 3:7 エラー箇所(3行目の7文字目)
エラーコード TS2322 TypeScriptのエラー番号
Type ‘X’ string 実際に代入しようとしている値の型
type ‘Y’ number 期待される型(変数の宣言型)

覚え方:「Type ‘代入しようとした型‘ is not assignable to type ‘期待されている型‘」と読みます。右側(Y)が正解で、左側(X)が間違いです。

なぜTS2322エラーが発生するのか

TypeScriptは構造的型付け(Structural Typing)を採用しています。これは、型の名前ではなく型の構造(シェイプ)によって互換性を判断する仕組みです。

TS2322が発生するのは、代入元の型が代入先の型の部分型(Subtype)ではないと判断されたときです。具体的には以下のような場合です。

ケース 具体例
基本型の不一致 stringnumber に代入
リテラル型の widening string"hello" に代入
オブジェクト構造の不一致 必須プロパティの欠如・型の不一致
配列要素型の不一致 string[]number[] に代入
ユニオン型の不一致 ユニオンに含まれない型の代入
関数の戻り値型の不一致 宣言した戻り値型と実際の返却値が異なる
ジェネリクスの型引数不一致 制約を満たさない型引数の指定

以降のセクションでは、これらのケースを一つずつ詳しく見ていきます。

基本的なケースと解決方法

まずは最も基本的な型の不一致から見ていきましょう。TypeScriptの基本型(string / number / boolean)の間で発生するTS2322は、最もシンプルで理解しやすいパターンです。

string を number に代入しようとした場合

文字列型の値を数値型の変数に代入しようとすると、TS2322が発生します。

error TS2322: Type ‘string’ is not assignable to type ‘number’.

NGコード
// string を number に代入しようとしている
const age: number = "25";  // TS2322 エラー

// フォーム入力値は string
const input = document.getElementById("age") as HTMLInputElement;
const userAge: number = input.value;  // TS2322 エラー(value は string)

原因:TypeScriptでは string 型と number 型は完全に別の型です。JavaScript では "25" を数値として扱えますが、TypeScript の型システムでは暗黙的な型変換は認められません。

OKコード(修正例)
// 方法1: Number() で明示的に変換
const age: number = Number("25");  // OK: 25

// 方法2: parseInt / parseFloat を使用
const age2: number = parseInt("25", 10);  // OK: 25

// 方法3: 単項プラス演算子
const age3: number = +"25";  // OK: 25

// フォーム入力値の変換
const input = document.getElementById("age") as HTMLInputElement;
const userAge: number = Number(input.value);  // OK

注意:Number("abc")NaN を返します。ユーザー入力を変換する場合は、変換後に isNaN() でバリデーションを行いましょう。

number を string に代入しようとした場合

逆に、数値型の値を文字列型の変数に代入しようとした場合もTS2322が発生します。

error TS2322: Type ‘number’ is not assignable to type ‘string’.

NGコード
const message: string = 100;  // TS2322 エラー

// APIレスポンスの数値をそのまま文字列変数に入れようとする
const statusCode: number = 200;
const statusText: string = statusCode;  // TS2322 エラー
OKコード(修正例)
// 方法1: String() で変換
const message: string = String(100);  // OK: "100"

// 方法2: .toString() メソッド
const message2: string = (100).toString();  // OK: "100"

// 方法3: テンプレートリテラル
const statusCode: number = 200;
const statusText: string = `${statusCode}`;  // OK: "200"

boolean への不正な代入

boolean 型には true または false のみ代入可能です。数値や文字列を直接代入しようとするとTS2322が発生します。

error TS2322: Type ‘number’ is not assignable to type ‘boolean’.

NGコード
// 数値を boolean に代入
const isActive: boolean = 1;  // TS2322 エラー

// 文字列を boolean に代入
const isValid: boolean = "true";  // TS2322 エラー

// 0 や "" は JavaScript では falsy だが boolean ではない
const isEmpty: boolean = 0;  // TS2322 エラー
OKコード(修正例)
// 方法1: Boolean() で変換
const isActive: boolean = Boolean(1);  // OK: true

// 方法2: 二重否定(!!)
const isValid: boolean = !!"true";  // OK: true

// 方法3: 比較演算子を使って明示的に boolean にする
const isEmpty: boolean = value === 0;  // OK: true or false

// 方法4: true / false を直接代入
const isActive2: boolean = true;  // OK

リテラル型への代入エラー

TypeScriptでは、特定の文字列や数値を型として使うリテラル型があります。一般的な string 型をリテラル型に代入しようとするとTS2322が発生します。

error TS2322: Type ‘string’ is not assignable to type ‘”hello” | “world”‘.

NGコード
type Greeting = "hello" | "world";

// let で宣言すると string に widening される
let word = "hello";  // 型は string("hello" ではない)
const greeting: Greeting = word;  // TS2322 エラー: string は "hello" | "world" に代入不可

// 関数の引数でも同様
function greet(g: Greeting) { console.log(g); }
let msg = "hello";
greet(msg);  // TS2322 相当のエラー

原因:let で変数を宣言すると、TypeScriptは値をwidening(拡大)して一般的な string 型として推論します。string 型は "hello" | "world" よりも広い型なので、代入できません。

OKコード(修正例)
type Greeting = "hello" | "world";

// 方法1: const で宣言(リテラル型が保持される)
const word = "hello";  // 型は "hello"(リテラル型)
const greeting: Greeting = word;  // OK

// 方法2: 変数に型注釈をつける
let word2: Greeting = "hello";  // 型は Greeting
const greeting2: Greeting = word2;  // OK

// 方法3: as const で widening を防ぐ
let word3 = "hello" as const;  // 型は "hello"
const greeting3: Greeting = word3;  // OK

null / undefined の代入エラー

strictNullChecks が有効(推奨設定)の場合、nullundefined を通常の型に代入しようとするとTS2322が発生します。

error TS2322: Type ‘null’ is not assignable to type ‘string’.

NGコード
// strictNullChecks: true の場合
const name: string = null;       // TS2322 エラー
const count: number = undefined;  // TS2322 エラー

// 関数の戻り値が null の可能性がある場合
function findUser(): string | null {
  return null;
}
const user: string = findUser();  // TS2322 エラー: string | null は string に代入不可
OKコード(修正例)
// 方法1: ユニオン型で null を許容
const name: string | null = null;  // OK

// 方法2: デフォルト値を設定
const user: string = findUser() ?? "unknown";  // OK: null の場合は "unknown"

// 方法3: 型ガードで null チェック
const result = findUser();
if (result !== null) {
  const user: string = result;  // OK: null が除外された
}

数値リテラル型の不一致

文字列リテラル型と同様に、数値にもリテラル型があります。HTTPステータスコードやポート番号など、特定の数値のみ許可する型定義でよく使われます。

error TS2322: Type ‘number’ is not assignable to type ‘200 | 404 | 500’.

NGコード
type StatusCode = 200 | 404 | 500;

let code = 200;  // 型は number
const status: StatusCode = code;  // TS2322 エラー
OKコード(修正例)
type StatusCode = 200 | 404 | 500;

// 方法1: const で宣言
const code = 200;  // 型は 200(リテラル型)
const status: StatusCode = code;  // OK

// 方法2: 型注釈をつける
let code2: StatusCode = 200;  // OK

// 方法3: as const
let code3 = 200 as const;  // 型は 200
const status3: StatusCode = code3;  // OK

ポイント:リテラル型のTS2322に共通する解決策は3つ。(1) const で宣言する(2) 変数に型注釈をつける(3) as const で widening を防ぐ。この3パターンを覚えておけば大半のケースに対応できます。

オブジェクト型でのTS2322

オブジェクト型の不一致は、TS2322が発生する最も一般的なシーンの一つです。プロパティの型不一致、余剰プロパティチェック、ネストしたオブジェクトの構造不一致など、さまざまなバリエーションがあります。

プロパティの型不一致

インターフェースやオブジェクト型で定義したプロパティの型と、実際に渡した値の型が異なる場合にTS2322が発生します。

error TS2322: Type ‘{ name: string; age: string; }’ is not assignable to type ‘User’.
Types of property ‘age’ are incompatible.
Type ‘string’ is not assignable to type ‘number’.

NGコード
interface User {
  name: string;
  age: number;
  email: string;
}

// age が string になっている
const user: User = {
  name: "田中太郎",
  age: "25",      // TS2322: string は number に代入不可
  email: "tanaka@example.com"
};

原因:TypeScriptはオブジェクトの各プロパティの型を個別にチェックします。age プロパティの期待される型は number ですが、文字列 "25" が代入されているためエラーになります。エラーメッセージには「Types of property ‘age’ are incompatible」と、どのプロパティが問題かも表示されます。

OKコード(修正例)
const user: User = {
  name: "田中太郎",
  age: 25,           // OK: number 型
  email: "tanaka@example.com"
};

必須プロパティの欠如

必須プロパティが欠けている場合は TS2741(Property ‘X’ is missing in type ‘Y’)が発生しますが、オブジェクトを変数に代入する際に型注釈と構造が合わない場合はTS2322として表示されることがあります。

error TS2322: Type ‘{ name: string; }’ is not assignable to type ‘User’.
Property ‘age’ is missing in type ‘{ name: string; }’ but required in type ‘User’.

NGコード
interface User {
  name: string;
  age: number;
  email: string;
}

// age と email が欠けている
const user: User = {
  name: "田中太郎"
};  // TS2322 エラー: age, email が不足
OKコード(修正例)
// 方法1: すべての必須プロパティを含める
const user: User = {
  name: "田中太郎",
  age: 25,
  email: "tanaka@example.com"
};

// 方法2: オプショナルプロパティに変更する
interface UserOptional {
  name: string;
  age?: number;   // オプショナル
  email?: string; // オプショナル
}

// 方法3: Partial<T> を使う
const partialUser: Partial<User> = {
  name: "田中太郎"
};  // OK: すべてのプロパティがオプショナルに

余剰プロパティチェック(Excess Property Checks)

TypeScriptでは、オブジェクトリテラルを直接代入する際に余剰プロパティチェックが働きます。定義されていないプロパティが含まれているとエラーになります。

error TS2322: Type ‘{ name: string; age: number; role: string; }’ is not assignable to type ‘User’.
Object literal may only specify known properties, and ‘role’ does not exist in type ‘User’.

NGコード
interface User {
  name: string;
  age: number;
}

// 余剰プロパティ role がある
const user: User = {
  name: "田中太郎",
  age: 25,
  role: "admin"   // TS2322 エラー: role は User に存在しない
};

原因:オブジェクトリテラルを型注釈付きの変数に直接代入する場合、TypeScriptは型に定義されていない余剰プロパティをエラーとして検出します。これはタイプミスの早期発見に役立つ安全機構です。

OKコード(修正例)
// 方法1: インターフェースに role を追加
interface UserWithRole {
  name: string;
  age: number;
  role?: string;  // 追加
}

// 方法2: 不要なプロパティを削除
const user: User = {
  name: "田中太郎",
  age: 25
};  // OK

// 方法3: 中間変数を使う(余剰プロパティチェックを回避)
const data = { name: "田中太郎", age: 25, role: "admin" };
const user2: User = data;  // OK: 中間変数では余剰プロパティチェックが働かない

// 方法4: インデックスシグネチャを使う
interface FlexibleUser {
  name: string;
  age: number;
  [key: string]: unknown;  // 任意のプロパティを許容
}

注意:方法3の「中間変数を使う」方法は余剰プロパティチェックを回避しますが、タイプミスの検出ができなくなるデメリットがあります。通常は方法1(インターフェースにプロパティを追加)が最も安全です。

オプショナルプロパティ関連のTS2322

オプショナルプロパティ(? 付き)は T | undefined を意味するため、undefined を許容しない型への代入でTS2322が発生することがあります。

error TS2322: Type ‘string | undefined’ is not assignable to type ‘string’.
Type ‘undefined’ is not assignable to type ‘string’.

NGコード
interface Config {
  host: string;
  port?: number;  // number | undefined
}

const config: Config = { host: "localhost", port: 3000 };

// port は number | undefined なので number に直接代入できない
const port: number = config.port;  // TS2322 エラー
OKコード(修正例)
// 方法1: デフォルト値を使う(Nullish Coalescing)
const port: number = config.port ?? 8080;  // OK: undefined なら 8080

// 方法2: 型ガードで undefined をチェック
if (config.port !== undefined) {
  const port: number = config.port;  // OK: undefined が除外された
}

// 方法3: undefined を許容する型にする
const port2: number | undefined = config.port;  // OK

ネストしたオブジェクトの型不一致

オブジェクトがネストしている場合、内側のオブジェクトの型が合わなくてもTS2322が発生します。エラーメッセージが長くなりがちですが、どのプロパティが問題かを追跡して読み解きましょう。

error TS2322: Type ‘{ street: string; city: number; }’ is not assignable to type ‘Address’.
Types of property ‘city’ are incompatible.
Type ‘number’ is not assignable to type ‘string’.

NGコード
interface Address {
  street: string;
  city: string;
  zipCode: string;
}

interface Company {
  name: string;
  address: Address;
}

const company: Company = {
  name: "株式会社テスト",
  address: {
    street: "渋谷1-2-3",
    city: 150,        // TS2322 エラー: number は string に代入不可
    zipCode: "150-0001"
  }
};
OKコード(修正例)
const company: Company = {
  name: "株式会社テスト",
  address: {
    street: "渋谷1-2-3",
    city: "東京都渋谷区",  // OK: string 型
    zipCode: "150-0001"
  }
};

インターフェースの実装ミス(クラス)

クラスがインターフェースを implements する際に、プロパティの型が合わない場合もTS2322が発生します。

error TS2322: Type ‘number’ is not assignable to type ‘string’.

NGコード
interface Printable {
  toString(): string;
  label: string;
}

class Product implements Printable {
  label: number = 100;  // TS2322: number は string に代入不可

  toString() {
    return `Product: ${this.label}`;
  }
}
OKコード(修正例)
class Product implements Printable {
  label: string = "商品A";  // OK: string 型

  toString() {
    return `Product: ${this.label}`;
  }
}

型エイリアスとインターフェースの構造不一致

TypeScriptは構造的型付けなので、型の名前が違っても構造が同じなら代入可能です。しかし、プロパティの型が1つでも異なるとTS2322が発生します。

例: 構造が同じなら代入OK、異なればエラー
interface Dog {
  name: string;
  age: number;
}

type Cat = {
  name: string;
  age: number;
};

const dog: Dog = { name: "ポチ", age: 3 };
const cat: Cat = dog;  // OK: 構造が同じなので代入可能

interface Fish {
  name: string;
  age: string;  // number ではなく string
}

const fish: Fish = dog;  // TS2322 エラー: age の型が異なる

配列・タプルでのTS2322

配列やタプルは要素の型が厳密にチェックされるため、TS2322が発生しやすいポイントです。配列全体の代入、個別要素の追加、readonly制約など、さまざまなケースを見ていきます。

配列要素型の不一致

異なる要素型の配列を代入しようとするとTS2322が発生します。

error TS2322: Type ‘string[]’ is not assignable to type ‘number[]’.
Type ‘string’ is not assignable to type ‘number’.

NGコード
// string[] を number[] に代入
const names: string[] = ["Alice", "Bob"];
const ids: number[] = names;  // TS2322 エラー

// 配列リテラルに異なる型の要素
const scores: number[] = [90, 85, "100"];  // TS2322: string は number に代入不可
OKコード(修正例)
// 正しい型の配列を代入
const ids: number[] = [1, 2, 3];  // OK

// すべての要素を number にする
const scores: number[] = [90, 85, 100];  // OK

// 文字列を数値に変換してから格納
const rawScores = ["90", "85", "100"];
const numScores: number[] = rawScores.map(Number);  // OK: ["90","85","100"] → [90,85,100]

// 混合型の配列が必要な場合はユニオン型を使う
const mixed: (string | number)[] = [90, "85", 100];  // OK

readonly 配列への代入

readonly 配列(ReadonlyArray<T> または readonly T[])は、通常の配列 T[] と型互換性に制限があります。

error TS2322: Type ‘readonly number[]’ is not assignable to type ‘number[]’.
The type ‘readonly number[]’ is ‘readonly’ and cannot be assigned to the mutable type ‘number[]’.

NGコード
const readonlyNumbers: readonly number[] = [1, 2, 3];

// readonly number[] を number[] に代入しようとする
const mutableNumbers: number[] = readonlyNumbers;  // TS2322 エラー

// 関数で mutable な配列を受け取る場合
function sortNumbers(nums: number[]): number[] {
  return nums.sort();
}
sortNumbers(readonlyNumbers);  // エラー: readonly配列は渡せない

原因:readonly number[] から number[] への代入は安全ではありません。number[] には push()sort() などの破壊的メソッドがあり、readonly の制約が失われてしまうからです。

OKコード(修正例)
// 方法1: readonly を維持する
const numbers: readonly number[] = readonlyNumbers;  // OK

// 方法2: スプレッド演算子でコピーして mutable にする
const mutableNumbers: number[] = [...readonlyNumbers];  // OK: 新しい配列

// 方法3: 関数の引数を readonly にする
function getSum(nums: readonly number[]): number {
  return nums.reduce((a, b) => a + b, 0);
}
getSum(readonlyNumbers);  // OK

ベストプラクティス:関数の引数で配列を受け取る場合、その関数内で配列を変更しないのであれば readonly T[] で受け取るのがベストです。こうすることで、readonly配列もmutable配列もどちらも受け取れるようになります。

タプル型の要素数・型の不一致

タプル型は「要素数」と「各位置の型」が固定された配列型です。通常の配列とは異なり、要素の型がインデックスごとに異なります。

error TS2322: Type ‘[string, string]’ is not assignable to type ‘[string, number]’.
Type at position 1 in source is not compatible with type at position 1 in target.
Type ‘string’ is not assignable to type ‘number’.

NGコード
// タプル: [名前, 年齢]
type NameAge = [string, number];

// 2番目の要素が string になっている
const pair: NameAge = ["田中", "25"];  // TS2322 エラー

// 要素数が多い
const pair2: NameAge = ["田中", 25, "extra"];  // TS2322 エラー: 要素数が合わない

// 要素数が少ない
const pair3: NameAge = ["田中"];  // TS2322 エラー: 要素が不足
OKコード(修正例)
type NameAge = [string, number];

// 正しい型と要素数
const pair: NameAge = ["田中", 25];  // OK

// 3番目の要素が必要な場合はタプル型を拡張
type NameAgeRole = [string, number, string];
const triple: NameAgeRole = ["田中", 25, "admin"];  // OK

// オプショナルなタプル要素
type FlexTuple = [string, number?];
const a: FlexTuple = ["田中"];       // OK: 2番目はオプショナル
const b: FlexTuple = ["田中", 25];  // OK

配列をタプルに代入する場合

通常の配列(string[])をタプル型に代入しようとすると、配列の長さが保証されないためTS2322が発生します。

error TS2322: Type ‘string[]’ is not assignable to type ‘[string, string]’.
Target requires 2 element(s) but source may have fewer.

NGコード
const arr: string[] = ["hello", "world"];

// string[] はタプル [string, string] に代入不可
const tuple: [string, string] = arr;  // TS2322 エラー
OKコード(修正例)
// 方法1: as const で推論をタプルにする
const arr = ["hello", "world"] as const;  // readonly ["hello", "world"]

// 方法2: 型アサーション
const arr2: string[] = ["hello", "world"];
const tuple = arr2 as [string, string];  // OK(安全性は自己責任)

// 方法3: 最初からタプル型で宣言
const tuple2: [string, string] = ["hello", "world"];  // OK

配列 vs タプルの違い:string[] は「0個以上のstring要素を持つ配列」、[string, string] は「ちょうど2個のstring要素を持つ配列」です。配列はタプルよりも広い型なので、タプルから配列への代入は可能ですが、配列からタプルへの代入は安全でないためエラーになります。

ユニオン型・リテラル型でのTS2322

ユニオン型やリテラル型は TypeScript の型システムの中核を担う機能です。型の絞り込み(Narrowing)が不十分な場合や、リテラル型の widening が予期せず発生した場合にTS2322が頻発します。

ユニオン型の絞り込み不足

ユニオン型の変数を、そのユニオンの一部の型にしか代入できない変数に渡す場合、型の絞り込み(Narrowing)を行わないとTS2322が発生します。

error TS2322: Type ‘string | number’ is not assignable to type ‘string’.
Type ‘number’ is not assignable to type ‘string’.

NGコード
function processValue(value: string | number) {
  // value は string | number なので string に直接代入できない
  const text: string = value;  // TS2322 エラー
}
OKコード(修正例)
function processValue(value: string | number) {
  // 方法1: typeof で型を絞り込む
  if (typeof value === "string") {
    const text: string = value;  // OK: この分岐では string
  }

  // 方法2: String() で変換
  const text2: string = String(value);  // OK: string | number → string

  // 方法3: 受け取る側をユニオン型にする
  const result: string | number = value;  // OK
}

リテラル型の widening(型の拡大)

let で変数を宣言すると、TypeScript はリテラル型を一般的な型に拡大(widening)します。これが原因でリテラル型やユニオンリテラル型への代入でTS2322が発生することがあります。

error TS2322: Type ‘string’ is not assignable to type ‘”GET” | “POST” | “PUT” | “DELETE”‘.

NGコード
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

// let で宣言すると string に widening される
let method = "GET";  // 型: string("GET" ではない)

const config: { method: HttpMethod } = {
  method: method  // TS2322 エラー: string は HttpMethod に代入不可
};

// オブジェクト内のプロパティも widening される
const request = {
  url: "https://api.example.com",
  method: "GET"  // 型: string("GET" ではない)
};

function fetchData(method: HttpMethod) { /* ... */ }
fetchData(request.method);  // エラー: string は HttpMethod に代入不可
OKコード(修正例)
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

// 方法1: const で宣言(リテラル型が保持される)
const method = "GET";  // 型: "GET"

// 方法2: 型注釈をつける
let method2: HttpMethod = "GET";  // 型: HttpMethod

// 方法3: as const でオブジェクト全体を readonly リテラル化
const request = {
  url: "https://api.example.com",
  method: "GET"
} as const;  // method の型は "GET"

fetchData(request.method);  // OK

// 方法4: プロパティ個別に as const
const request2 = {
  url: "https://api.example.com",
  method: "GET" as const  // method だけリテラル型
};

as const の使い方と注意点

as const は TypeScript 3.4 で導入されたconst アサーションです。リテラル型の widening を防ぎ、オブジェクトや配列のすべての値を readonly リテラル型にします。

as const の効果
// as const なし
const colors = ["red", "green", "blue"];
// 型: string[]

// as const あり
const colorsConst = ["red", "green", "blue"] as const;
// 型: readonly ["red", "green", "blue"]

// as const なし(オブジェクト)
const config = { env: "production", port: 3000 };
// 型: { env: string; port: number; }

// as const あり(オブジェクト)
const configConst = { env: "production", port: 3000 } as const;
// 型: { readonly env: "production"; readonly port: 3000; }

注意:as const はすべてのプロパティを readonly にするため、後から値を変更できなくなります。値の変更が必要な場合は、let method: HttpMethod = "GET" のように型注釈を使いましょう。

Discriminated Union(判別ユニオン)での不一致

Discriminated Union は共通のプロパティ(判別子)を持つ複数のオブジェクト型のユニオンです。判別子の値が合わない場合にTS2322が発生します。

error TS2322: Type ‘{ type: “rectangle”; width: number; }’ is not assignable to type ‘Shape’.
Property ‘height’ is missing in type ‘{ type: “rectangle”; width: number; }’ but required in type ‘Rectangle’.

NGコード
interface Circle {
  type: "circle";
  radius: number;
}

interface Rectangle {
  type: "rectangle";
  width: number;
  height: number;
}

type Shape = Circle | Rectangle;

// height が欠けている
const shape: Shape = {
  type: "rectangle",
  width: 100
};  // TS2322 エラー: height が不足

// 存在しない type 値
const shape2: Shape = {
  type: "triangle",  // TS2322: "triangle" は "circle" | "rectangle" に代入不可
  sides: 3
};
OKコード(修正例)
// すべての必須プロパティを含める
const shape: Shape = {
  type: "rectangle",
  width: 100,
  height: 50  // height を追加
};  // OK

// 新しい Shape を追加する場合は型定義を拡張
interface Triangle {
  type: "triangle";
  sides: number;
}

type ExtendedShape = Circle | Rectangle | Triangle;

ユニオン型の網羅性チェック(Exhaustive Check)

switch文やif文でユニオン型の全パターンを処理する場合、never 型を活用した網羅性チェックでTS2322が意図的に発生することがあります。これはバグの早期発見に有用なテクニックです。

網羅性チェックの例
type Status = "active" | "inactive" | "pending";

function handleStatus(status: Status): string {
  switch (status) {
    case "active":
      return "有効";
    case "inactive":
      return "無効";
    // "pending" を処理し忘れている場合...
    default: {
      // status の型が never でない場合はエラーになる
      const _exhaustive: never = status;
      // TS2322: Type '"pending"' is not assignable to type 'never'
      return _exhaustive;
    }
  }
}

ポイント:この場合のTS2322は「エラーが出るべき正しい動作」です。default 節で never 型への代入を行うことで、switch 文で処理し忘れたケースがある場合にコンパイル時にエラーとして検出できます。新しいステータスを追加したときの修正漏れを防ぐ強力なテクニックです。

交差型(Intersection Type)での不一致

交差型 A & B は型 A と型 B の両方の制約を満たす型です。どちらか一方の制約が満たされていない場合にTS2322が発生します。

error TS2322: Type ‘{ name: string; age: number; }’ is not assignable to type ‘Named & Aged & Employed’.
Property ‘company’ is missing in type ‘{ name: string; age: number; }’.

NGコード
interface Named { name: string; }
interface Aged { age: number; }
interface Employed { company: string; }

type Employee = Named & Aged & Employed;

// company プロパティが欠けている
const emp: Employee = {
  name: "田中",
  age: 30
};  // TS2322 エラー: company が不足
OKコード(修正例)
const emp: Employee = {
  name: "田中",
  age: 30,
  company: "株式会社テスト"  // すべてのプロパティを含める
};  // OK

関数の戻り値でのTS2322

関数の戻り値の型注釈と、実際に return している値の型が合わない場合にTS2322が発生します。async 関数、コールバック、void vs undefined の違いなど、関数特有のパターンを見ていきましょう。

戻り値の型が合わない

最も基本的なパターンです。関数の戻り値型注釈と return 文の値の型が異なるとTS2322が発生します。

error TS2322: Type ‘number’ is not assignable to type ‘string’.

NGコード
// 戻り値型が string なのに number を返している
function getUserName(id: number): string {
  return id;  // TS2322: number は string に代入不可
}

// 条件分岐で一部だけ型が合わない
function getStatus(code: number): string {
  if (code === 200) return "OK";
  if (code === 404) return "Not Found";
  return code;  // TS2322: number は string に代入不可
}
OKコード(修正例)
// 方法1: 正しい型の値を返す
function getUserName(id: number): string {
  return `User_${id}`;  // OK: string を返す
}

// 方法2: 戻り値型をユニオンに変更
function getStatus(code: number): string {
  if (code === 200) return "OK";
  if (code === 404) return "Not Found";
  return `Unknown (${code})`;  // OK: string を返す
}

Promise / async 関数での型不一致

async 関数は自動的に Promise<T> を返します。戻り値型を Promise<T> にした場合、return する値は T 型でなければなりません。

error TS2322: Type ‘number’ is not assignable to type ‘string’.

NGコード
// async 関数で型が合わない
async function fetchUserName(id: number): Promise<string> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();
  return data.id;  // id が number の場合、TS2322 エラー
}

// Promise を返すべきところで非 Promise を返す
function getDataAsync(): Promise<string> {
  return "hello";  // TS2322: string は Promise<string> に代入不可
}
OKコード(修正例)
// async 関数では return する値の型に注意
async function fetchUserName(id: number): Promise<string> {
  const response = await fetch(`/api/users/${id}`);
  const data = await response.json();
  return data.name;  // OK: name が string なら問題なし
}

// 非 async 関数で Promise を返す場合
function getDataAsync(): Promise<string> {
  return Promise.resolve("hello");  // OK: Promise<string> を返す
}

// または async を使う
async function getDataAsync2(): Promise<string> {
  return "hello";  // OK: async が自動で Promise.resolve() する
}

コールバック関数の型不一致

コールバック関数を渡す場合、そのコールバックの型(引数・戻り値)が期待される型と合わないとTS2322が発生します。

error TS2322: Type ‘(value: string) => number’ is not assignable to type ‘(value: string) => string’.
Type ‘number’ is not assignable to type ‘string’.

NGコード
type Formatter = (value: string) => string;

// 戻り値が number の関数を string を返すべきコールバックに代入
const toLength: Formatter = (value: string) => value.length;  // TS2322

// Array.map のコールバックでの型不一致
const items = ["a", "b", "c"];
const result: string[] = items.map((item) => item.length);  // TS2322: number[] は string[] に代入不可
OKコード(修正例)
// コールバックの戻り値型を合わせる
const toUpper: Formatter = (value: string) => value.toUpperCase();  // OK: string を返す

// map の結果型を合わせる
const result: number[] = items.map((item) => item.length);  // OK: number[] に変更

// または変換して string[] にする
const result2: string[] = items.map((item) => String(item.length));  // OK

void vs undefined の違い

voidundefined は似ているようで異なる型です。特にコールバックの戻り値型で混同しやすいポイントです。

error TS2322: Type ‘void’ is not assignable to type ‘undefined’.

NGコード
// void を返す関数の戻り値を undefined 型に代入
function doSomething(): void {
  console.log("done");
}

const result: undefined = doSomething();  // TS2322: void は undefined に代入不可

// コールバックの型で undefined を期待しているが void の関数を渡す
type Callback = () => undefined;
const cb: Callback = doSomething;  // TS2322 エラー

原因:void は「戻り値を使わない」ことを示す型であり、undefined は「値として undefined を返す」型です。void を返す関数の戻り値を変数に代入して使うことは想定されていません。

OKコード(修正例)
// 方法1: void を返す場合は戻り値を変数に入れない
doSomething();  // OK: 戻り値を使わない

// 方法2: 明示的に undefined を返す関数にする
function doSomethingV2(): undefined {
  console.log("done");
  return undefined;
}
const result: undefined = doSomethingV2();  // OK

// 方法3: コールバック型を void にする
type Callback = () => void;
const cb: Callback = doSomething;  // OK
意味 return 文
void 戻り値を使わないことを示す return; または return 文なし
undefined 値として undefined を返す return undefined;
never 関数が正常終了しない 例外を投げるか無限ループ

アロー関数の暗黙的な return

アロー関数で {} を省略した場合、式の結果が暗黙的に return されます。この暗黙の戻り値と期待される型が合わない場合にTS2322が発生します。

注意すべきパターン
type VoidCallback = () => void;

// void を返すコールバック型には何を return しても OK(特殊ルール)
const cb1: VoidCallback = () => 42;      // OK(戻り値は無視される)
const cb2: VoidCallback = () => "hello"; // OK(戻り値は無視される)

// ただし、void を直接返す関数宣言では return 値を使えない
function myVoidFn(): void {
  return 42;  // TS2322: number は void に代入不可
}

void コールバックの特殊ルール:コールバック型(関数型リテラル)の戻り値が void の場合、TypeScriptはどんな値を return しても許可します。これは Array.forEach のコールバックなどで、戻り値を意図せず return してしまっても問題にならないようにするための設計です。ただし、関数宣言や関数式で : void を戻り値に指定した場合はこの特殊ルールは適用されません。

ジェネリクスでのTS2322

ジェネリクス(Generics)は型をパラメータとして受け取る仕組みです。型引数の制約違反や型推論の失敗、条件型での不一致など、ジェネリクス特有のTS2322パターンを解説します。

型引数の制約違反

ジェネリクスに extends で型制約を付けている場合、制約を満たさない型引数を指定するとTS2322が発生します。

error TS2322: Type ‘number’ is not assignable to type ‘T’.
‘number’ is assignable to the constraint of type ‘T’, but ‘T’ could be instantiated with a different subtype of constraint ‘{ id: number }’.

NGコード
// T は { id: number } を拡張する型
function createEntity<T extends { id: number }>(id: number): T {
  return { id };  // TS2322: { id: number } は T に代入不可
}

// T は string であるべきだが number を返している
function identity<T>(value: T): T {
  return 42;  // TS2322: number は T に代入不可
}

原因:ジェネリクスの型パラメータ T は呼び出し時に具体的な型に決まります。T extends { id: number } という制約があっても、T{ id: number; name: string } のようなより詳細な型かもしれません。{ id } だけを返すと name が欠けてしまう可能性があるため、TypeScriptはエラーにします。

OKコード(修正例)
// 方法1: 戻り値型をジェネリクスではなく具体的な型にする
function createEntity(id: number): { id: number } {
  return { id };  // OK
}

// 方法2: 引数から T を受け取って返す
function cloneEntity<T extends { id: number }>(entity: T): T {
  return { ...entity };  // OK: スプレッドで T の全プロパティをコピー
}

// identity 関数は引数をそのまま返す
function identity<T>(value: T): T {
  return value;  // OK: 引数をそのまま返す
}

ジェネリクスの型推論の失敗

TypeScriptが型引数を正しく推論できない場合にTS2322が発生することがあります。特に複数の型パラメータがある場合や、条件型と組み合わせる場合に起こりがちです。

error TS2322: Type ‘string’ is not assignable to type ‘T[K]’.

NGコード
function setProperty<
  T extends Record<string, unknown>,
  K extends keyof T
>(obj: T, key: K, value: string): void {
  obj[key] = value;  // TS2322: string は T[K] に代入不可
}

原因:T[K]T のキー K に対応する値の型です。T[K]string とは限らないため(number かもしれないし boolean かもしれない)、string を代入することはできません。

OKコード(修正例)
// 方法1: value の型を T[K] にする
function setProperty<
  T extends Record<string, unknown>,
  K extends keyof T
>(obj: T, key: K, value: T[K]): void {
  obj[key] = value;  // OK: value の型は T[K]
}

// 使用例
const user = { name: "田中", age: 25 };
setProperty(user, "name", "佐藤");  // OK: T[K] = string
setProperty(user, "age", 30);     // OK: T[K] = number

条件型(Conditional Types)での不一致

条件型 T extends U ? X : Y を使った場合、TypeScriptが型を正しく解決できず、TS2322が発生することがあります。

error TS2322: Type ‘string’ is not assignable to type ‘T extends string ? string : number’.

NGコード
type Result<T> = T extends string ? string : number;

function processValue<T>(value: T): Result<T> {
  if (typeof value === "string") {
    return value.toUpperCase();  // TS2322: string は Result<T> に代入不可
  }
  return 0;  // TS2322: number は Result<T> に代入不可
}

原因:TypeScriptは関数の本体内では条件型を解決できません。T がまだ具体的な型に確定していないため、Result<T> も未解決のままとなり、stringnumber を代入しようとしてもエラーになります。

OKコード(修正例)
// 方法1: オーバーロードを使う
function processValue(value: string): string;
function processValue(value: number): number;
function processValue(value: string | number): string | number {
  if (typeof value === "string") {
    return value.toUpperCase();  // OK
  }
  return 0;  // OK
}

// 方法2: 型アサーション(条件型の制限を回避)
function processValueV2<T>(value: T): Result<T> {
  if (typeof value === "string") {
    return value.toUpperCase() as Result<T>;  // OK(型アサーション)
  }
  return 0 as Result<T>;  // OK(型アサーション)
}

注意:条件型を返す関数で as を使う場合は、型の安全性が自己責任になります。可能であればオーバーロードの方が安全です。条件型に関するこの制限はTypeScriptの既知の仕様であり、将来のバージョンで改善される可能性があります。

ジェネリクスとユーティリティ型の組み合わせ

Partial<T>Required<T>Pick<T, K> などのユーティリティ型とジェネリクスを組み合わせた際にTS2322が発生するケースです。

よくあるパターンと解決法
interface User {
  id: number;
  name: string;
  email: string;
}

// Partial<User> を User に代入しようとするとエラー
const partial: Partial<User> = { name: "田中" };
const user: User = partial;  // TS2322: Partial<User> は User に代入不可

// 解決: デフォルト値でマージする
const defaultUser: User = { id: 0, name: "", email: "" };
const user2: User = { ...defaultUser, ...partial };  // OK

// Required<T> を使って Partial を打ち消す
function ensureComplete<T>(partial: Partial<T>, defaults: T): T {
  return { ...defaults, ...partial };
}

React / JSX でのTS2322

React + TypeScript の開発では、コンポーネントの Props、イベントハンドラ、Ref、children など、さまざまな場面でTS2322が発生します。Reactに特有のパターンを見ていきましょう。

Props の型不一致

コンポーネントに渡す Props の型が定義と合わない場合、TS2322が発生します。React + TypeScript で最も頻繁に遭遇するパターンです。

error TS2322: Type ‘number’ is not assignable to type ‘string’.

NGコード
interface ButtonProps {
  label: string;
  onClick: () => void;
  variant?: "primary" | "secondary";
}

const Button: React.FC<ButtonProps> = ({ label, onClick, variant }) => (
  <button onClick={onClick}>{label}</button>
);

// Props の型が合わない
<Button
  label={123}          {/* TS2322: number は string に代入不可 */}
  onClick={"click"}    {/* TS2322: string は () => void に代入不可 */}
  variant={"danger"}   {/* TS2322: "danger" は "primary" | "secondary" に代入不可 */}
/>
OKコード(修正例)
<Button
  label={"送信"}                      {/* OK: string */}
  onClick={() => console.log("clicked")}  {/* OK: () => void */}
  variant={"primary"}                  {/* OK: "primary" | "secondary" */}
/>

イベントハンドラの型不一致

Reactのイベントハンドラには専用の型があります。DOM のネイティブイベントと React のSyntheticEvent を混同するとTS2322が発生します。

error TS2322: Type ‘(e: MouseEvent) => void’ is not assignable to type ‘MouseEventHandler<HTMLButtonElement>’.

NGコード
// DOM の MouseEvent を使っている(React の MouseEvent ではない)
const handleClick = (e: MouseEvent) => {
  console.log(e.target);
};

// React コンポーネントで使うとエラー
<button onClick={handleClick}>Click</button>  {/* TS2322 */}

// onChange の型が合わない
const handleChange = (e: Event) => {
  console.log(e.target);
};
<input onChange={handleChange} />  {/* TS2322 */}
OKコード(修正例)
// React.MouseEvent を使う
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {
  console.log(e.target);
};
<button onClick={handleClick}>Click</button>  {/* OK */}

// React.ChangeEvent を使う
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};
<input onChange={handleChange} />  {/* OK */}
イベント React の型 よく使うHTML要素
onClick React.MouseEvent<T> HTMLButtonElement
onChange React.ChangeEvent<T> HTMLInputElement
onSubmit React.FormEvent<T> HTMLFormElement
onKeyDown React.KeyboardEvent<T> HTMLInputElement
onFocus React.FocusEvent<T> HTMLInputElement

children の型不一致

React コンポーネントの children プロパティの型も、TS2322 の原因になりがちです。

children の型パターン
// children を string のみに制限
interface TitleProps {
  children: string;  // 文字列のみ許可
}

const Title = ({ children }: TitleProps) => <h1>{children}</h1>;

// JSX要素を渡すとエラー
<Title><span>Hello</span></Title>  {/* TS2322: ReactElement は string に代入不可 */}

// 修正: React.ReactNode を使う(何でも受け取れる)
interface TitlePropsFixed {
  children: React.ReactNode;  // 文字列、数値、JSX要素、配列、null、undefined を許可
}

// または PropsWithChildren を使う
type TitlePropsV2 = React.PropsWithChildren<{
  className?: string;
}>;

Ref の型不一致

useRef の型引数と、Ref を割り当てるHTML要素の型が合わない場合にTS2322が発生します。

error TS2322: Type ‘MutableRefObject<HTMLDivElement | null>’ is not assignable to type ‘LegacyRef<HTMLInputElement> | undefined’.

NGコード
const inputRef = useRef<HTMLDivElement>(null);

// HTMLDivElement の ref を input要素に渡している
return <input ref={inputRef} />;  {/* TS2322 エラー */}
OKコード(修正例)
// 正しいHTML要素の型を指定
const inputRef = useRef<HTMLInputElement>(null);
return <input ref={inputRef} />;  {/* OK */}

// よく使う HTML要素と型の対応
// <div>    → HTMLDivElement
// <input>  → HTMLInputElement
// <button> → HTMLButtonElement
// <form>   → HTMLFormElement
// <a>      → HTMLAnchorElement
// <img>    → HTMLImageElement
// <select> → HTMLSelectElement
// <textarea> → HTMLTextAreaElement

コンポーネントの戻り値型

React コンポーネントが返す JSX の型が合わない場合もTS2322が発生します。

よくあるパターン
// 条件分岐で undefined を返してしまう
const UserCard: React.FC<UserProps> = ({ user }) => {
  if (!user) {
    return undefined;  // TS2322: undefined は ReactElement に代入不可
  }
  return <div>{user.name}</div>;
};

// 修正: null を返す
const UserCardFixed: React.FC<UserProps> = ({ user }) => {
  if (!user) {
    return null;  // OK: React コンポーネントは null を返せる
  }
  return <div>{user.name}</div>;
};

useState の型不一致

useState の初期値から型が推論されるため、後から異なる型の値を set しようとするとTS2322が発生します。

NGコード → OKコード
// NG: 初期値 null から型が null のみに推論される
const [user, setUser] = useState(null);
setUser({ name: "田中" });  // TS2322: { name: string } は null に代入不可

// OK: ジェネリクスで型を明示
interface User { name: string; }
const [user2, setUser2] = useState<User | null>(null);
setUser2({ name: "田中" });  // OK

// NG: 初期値 "" から string に推論される
const [status, setStatus] = useState("");
setStatus(404);  // TS2322: number は string に代入不可

// OK: ユニオン型を明示
const [status2, setStatus2] = useState<string | number>("");
setStatus2(404);  // OK

ベストプラクティス:useState の初期値が null や空の値の場合は、必ずジェネリクスで型を明示しましょう。useState<User | null>(null) のように書くことで、後から正しい型の値を set できるようになります。

外部ライブラリとのTS2322

外部ライブラリ(npm パッケージ)を使用する際に、型定義のバージョン不一致や型定義の欠如が原因でTS2322が発生することがあります。

@types パッケージのバージョン不一致

ライブラリ本体と @types パッケージのバージョンが合っていない場合、型定義が実際のAPIと異なりTS2322が発生することがあります。

問題のあるバージョン組み合わせ
// package.json
{
  "dependencies": {
    "react": "^18.2.0",
    "@types/react": "^17.0.0"   // バージョンが合っていない!
  }
}
解決方法
# @types パッケージを最新に更新
npm install @types/react@latest @types/react-dom@latest

# 特定のバージョンを指定(ライブラリと合わせる)
npm install @types/react@^18.0.0

# バージョンの確認
npm ls @types/react

型定義がないライブラリ

型定義が提供されていないライブラリを使用すると、暗黙的に any 型になります。noImplicitAny: true の場合はエラーが出ますし、他の型注釈と組み合わせるとTS2322が発生することがあります。

対処法1: @types パッケージを探す
# @types パッケージがあるか確認
npm search @types/ライブラリ名

# あればインストール
npm install --save-dev @types/ライブラリ名
対処法2: declare module で型定義を書く
// src/types/custom-lib.d.ts

// 最小限の型定義(any で逃げるパターン)
declare module "custom-lib" {
  const lib: any;
  export default lib;
}

// より詳細な型定義
declare module "custom-lib" {
  export function parse(input: string): Record<string, unknown>;
  export function stringify(obj: unknown): string;
}
対処法3: tsconfig.json の設定
// tsconfig.json
{
  "compilerOptions": {
    // 型定義のない import を許可(推奨しない)
    "noImplicitAny": false,

    // カスタム型定義ファイルの場所を指定
    "typeRoots": ["./src/types", "./node_modules/@types"]
  }
}

ポイント:型定義の問題でTS2322が出た場合の対処優先順位は (1) @types パッケージをインストール → (2) declare module で型を書く → (3) 最終手段として as any です。

アンチパターンと正しい解決法

TS2322が発生したとき、手っ取り早く黙らせる方法はいくつかあります。しかし、その多くは型安全性を犠牲にするアンチパターンです。ここでは、やってはいけない方法と、正しい解決法を対比して解説します。

as による型アサーションの危険性

as(型アサーション)は TypeScript の型チェックを上書きします。TS2322を黙らせる最も手軽な方法ですが、実行時エラーの原因になります。

アンチパターン: as でエラーを黙らせる
interface User {
  name: string;
  age: number;
}

// as で型チェックを無視(危険!)
const user = { name: "田中" } as User;  // エラーは出ないが age が undefined

// 実行時に undefined エラーが発生する
console.log(user.age.toFixed(2));  // Runtime Error: Cannot read property 'toFixed' of undefined

// さらに危険: as unknown as T(ダブルアサーション)
const num: number = "hello" as unknown as number;  // どんな型にもなれてしまう
正しい解決法
// 方法1: 全プロパティを含める
const user: User = { name: "田中", age: 25 };  // OK

// 方法2: Partial を使う
const partialUser: Partial<User> = { name: "田中" };  // OK

// 方法3: satisfies を使う(TypeScript 4.9+)
const user2 = {
  name: "田中",
  age: 25
} satisfies User;  // OK: 型チェックしつつ、推論型を保持

as を使ってもいい場面:外部APIのレスポンスなど、TypeScriptが型を推論できないが開発者がその構造を確実に知っている場合に限り、as は許容されます。その場合でも Zodio-ts などのランタイムバリデーションライブラリと併用するのがベストです。

any で逃げるリスク

any 型はすべての型チェックを無効化します。TS2322に限らず、すべてのTypeScriptエラーを黙らせてしまう「万能薬」ですが、使いすぎるとTypeScriptを使う意味がなくなります。

アンチパターン: any を乱用
// any にすればエラーは出ない...が型安全性ゼロ
const data: any = fetchData();
const name: string = data.user.name;  // コンパイルは通るが...
// data.user が undefined なら実行時エラー

// 関数引数を any にする
function process(input: any): any {
  return input.something.else();  // 何でも通るが何も保証されない
}
正しい解決法: unknown を使う
// unknown は any の安全な代替
const data: unknown = fetchData();

// unknown はそのまま使えない → 型を絞り込む必要がある
if (typeof data === "object" && data !== null && "name" in data) {
  const name = (data as { name: string }).name;  // 型を確認してからアクセス
}

// Zod でランタイムバリデーション
import { z } from "zod";

const UserSchema = z.object({
  name: z.string(),
  age: z.number(),
});

const result = UserSchema.safeParse(data);
if (result.success) {
  const user = result.data;  // 型安全: { name: string; age: number }
}

@ts-ignore / @ts-expect-error は最終手段

コメントディレクティブ // @ts-ignore// @ts-expect-error は、その次の行のTypeScriptエラーを無視します。TS2322を含むすべてのエラーを黙らせることができますが、根本的な解決にはなりません

@ts-ignore vs @ts-expect-error
// @ts-ignore: エラーがあってもなくても無視する
// @ts-ignore
const age: number = "25";  // エラーが出ない

// @ts-expect-error: エラーがある場合のみ無視する(エラーがなくなったら警告)
// @ts-expect-error - ライブラリの型定義が不正確なため回避
const result = lib.buggyMethod();
方法 推奨度 説明
型定義を修正する 最推奨 根本原因を特定し、型を正しく書く
型ガード / Narrowing 推奨 typeof / instanceof / in で型を絞り込む
ユーティリティ型 推奨 Partial / Required / Pick / Omit を活用
satisfies 演算子 推奨 型チェックしつつ推論型を保持(TS 4.9+)
as 型アサーション 注意 型を確信できる場合のみ使用
@ts-expect-error 非推奨 ライブラリの型定義バグの一時回避用
@ts-ignore 非推奨 エラーが消えても気づけない
any を使う 非推奨 型安全性が完全に失われる

実務でよくあるTS2322パターン10選

ここまで型の種類ごとにTS2322パターンを見てきましたが、実務の開発現場で特に遭遇頻度が高いパターンを厳選して10個紹介します。

パターン1: APIレスポンスの型不一致

外部APIからのレスポンスを型付きの変数に代入する際に、レスポンスの構造と型定義が合わない場合です。

よくある問題と解決法
interface ApiResponse {
  id: number;
  name: string;
  email: string;
}

// NG: fetch の json() は any を返すのでコンパイルは通るが型安全でない
const response = await fetch("/api/user");
const data: ApiResponse = await response.json();  // 型チェックなし!

// OK: Zodでランタイムバリデーション
import { z } from "zod";

const ApiResponseSchema = z.object({
  id: z.number(),
  name: z.string(),
  email: z.string().email(),
});

const raw = await response.json();
const data = ApiResponseSchema.parse(raw);  // 型安全 + ランタイム検証

パターン2: 環境変数(string | undefined → string)

process.env のプロパティは string | undefined 型です。string 型の変数に直接代入するとTS2322が発生します。

error TS2322: Type ‘string | undefined’ is not assignable to type ‘string’.

NGコード → OKコード
// NG
const apiKey: string = process.env.API_KEY;  // TS2322

// OK: デフォルト値
const apiKey: string = process.env.API_KEY ?? "";

// OK: 存在チェックでエラーハンドリング
const apiKey2 = process.env.API_KEY;
if (!apiKey2) {
  throw new Error("API_KEY is not set");
}
// ここでは string 型に絞り込まれる
const key: string = apiKey2;  // OK

パターン3: Date型関連

Date オブジェクトと文字列の混同は、日付処理でよくあるTS2322パターンです。

Date 型の TS2322
// NG: Date オブジェクトを string に代入
const now: string = new Date();  // TS2322: Date は string に代入不可

// OK: 文字列に変換
const now1: string = new Date().toISOString();  // "2024-01-15T10:30:00.000Z"
const now2: string = new Date().toLocaleDateString();  // "2024/1/15"

// NG: 文字列を Date に代入
const date: Date = "2024-01-15";  // TS2322

// OK: Date オブジェクトを生成
const date2: Date = new Date("2024-01-15");  // OK

パターン4: enum 関連

TypeScript の enum は数値または文字列のリテラルですが、素の数値や文字列とは型互換性がありません。

enum の TS2322
enum Status {
  Active = "ACTIVE",
  Inactive = "INACTIVE",
}

// NG: 文字列は enum に直接代入できない
const status: Status = "ACTIVE";  // TS2322: string は Status に代入不可

// OK: enum メンバーを使う
const status2: Status = Status.Active;  // OK

// APIレスポンスの文字列を enum に変換したい場合
const apiStatus: string = "ACTIVE";
const status3: Status = apiStatus as Status;  // 型アサーション(値の検証は自己責任)

// より安全: バリデーション関数を作る
function toStatus(value: string): Status {
  if (Object.values(Status).includes(value as Status)) {
    return value as Status;
  }
  throw new Error(`Invalid status: ${value}`);
}

パターン5: null と undefined の混同

nullundefined は TypeScript では別の型です。strictNullChecks: true の場合、互いに代入できません。

null vs undefined
// NG: null を undefined 型に代入
const a: undefined = null;  // TS2322

// NG: undefined を null 型に代入
const b: null = undefined;  // TS2322

// API は null を返すが、オプショナルプロパティは undefined
interface ApiUser {
  name: string;
  bio: string | null;  // API は null を返す
}

interface DisplayUser {
  name: string;
  bio?: string;  // string | undefined
}

const apiUser: ApiUser = { name: "田中", bio: null };
const displayUser: DisplayUser = apiUser;  // TS2322: null は string | undefined に代入不可

// OK: null を undefined に変換
const displayUser2: DisplayUser = {
  ...apiUser,
  bio: apiUser.bio ?? undefined  // null → undefined
};

パターン6: Map / Set の型

Map.get() は T | undefined を返す
const map = new Map<string, number>();
map.set("a", 1);

// NG: get() は number | undefined を返す
const value: number = map.get("a");  // TS2322

// OK: デフォルト値
const value2: number = map.get("a") ?? 0;

// OK: 存在チェック
if (map.has("a")) {
  // TypeScript 5.x 以降は has() 後に get() の型が絞り込まれないので注意
  const value3 = map.get("a")!;  // Non-null assertion(has 確認済み)
}

パターン7: JSON.parse の戻り値

JSON.parse() は any を返す
// JSON.parse は any を返すのでTS2322にはならないが型安全でない
const data = JSON.parse('{"name": "田中"}');  // 型: any

// 安全にする方法1: 型アサーション + バリデーション
interface User { name: string; }

function parseUser(json: string): User {
  const parsed: unknown = JSON.parse(json);
  if (
    typeof parsed === "object" &&
    parsed !== null &&
    "name" in parsed &&
    typeof (parsed as User).name === "string"
  ) {
    return parsed as User;
  }
  throw new Error("Invalid JSON");
}

パターン8: Object.keys / Object.entries の戻り値型

Object.keys は string[] を返す
interface User {
  name: string;
  age: number;
}

const user: User = { name: "田中", age: 25 };

// NG: Object.keys は string[] を返す(keyof User ではない)
const keys: (keyof User)[] = Object.keys(user);  // TS2322

// OK: 型アサーション
const keys2 = Object.keys(user) as (keyof User)[];  // OK

// OK: ヘルパー関数
function typedKeys<T extends object>(obj: T): (keyof T)[] {
  return Object.keys(obj) as (keyof T)[];
}

パターン9: Promise.all / Promise.race の型

Promise.all の型推論
// Promise.all はタプル型を返す
const [users, posts] = await Promise.all([
  fetchUsers(),   // Promise<User[]>
  fetchPosts(),   // Promise<Post[]>
]);
// users: User[], posts: Post[]  ← 正しく推論される

// NG: 結果を間違った型に代入
const result: User[] = await Promise.all([
  fetchUsers(),
  fetchPosts()
]);  // TS2322: [User[], Post[]] は User[] に代入不可

// OK: 分割代入で正しい型を使う
const [users2, posts2] = await Promise.all([
  fetchUsers(),
  fetchPosts()
]);  // OK

パターン10: フォーム値のパース(HTMLInputElement.value は常に string)

フォーム値の型変換
interface FormData {
  name: string;
  age: number;
  isAdmin: boolean;
}

// NG: フォームの値はすべて string
const formData: FormData = {
  name: nameInput.value,      // OK (string)
  age: ageInput.value,        // TS2322: string は number に代入不可
  isAdmin: adminInput.checked  // OK (checkbox の checked は boolean)
};

// OK: 明示的に変換
const formData2: FormData = {
  name: nameInput.value,
  age: Number(ageInput.value),  // string → number
  isAdmin: adminInput.checked
};

まとめ

TS2322(Type 'X' is not assignable to type 'Y')は、TypeScript開発で最も遭遇するエラーです。この記事で解説した主要なパターンと解決法を振り返りましょう。

パターン 主な原因 解決策
基本型の不一致 string / number / boolean の混同 Number(), String(), Boolean() で変換
リテラル型の widening let で宣言すると型が広がる const 宣言 / as const / 型注釈
オブジェクトの不一致 プロパティ型・余剰プロパティ 型定義の見直し / Partial / Pick
配列・タプル 要素型・要素数の不一致 正しい型の配列 / readonly 対応
ユニオン型 型の絞り込み不足 typeof / instanceof で Narrowing
関数の戻り値 return の型不一致 / void 混同 戻り値型の見直し / Promise 対応
ジェネリクス 型引数の制約 / 条件型の未解決 オーバーロード / 型パラメータの見直し
React/JSX Props / イベント / Ref の型 React の型を正しく使用
外部ライブラリ @types のバージョン / 型定義欠如 @types 更新 / declare module
null / undefined strictNullChecks での不一致 ?? / 型ガード / ユニオン型

TS2322を解決する3ステップ

  1. エラーメッセージを読む:Type ‘X‘(実際の型)is not assignable to type ‘Y‘(期待される型)を確認
  2. 原因を特定する:X と Y の型がなぜ異なるのかを調べる(widening? 変換忘れ? 型定義の誤り?)
  3. 正しい方法で修正する:型ガード・変換・型定義の修正など、asany に頼らない方法を優先

TS2322 vs 関連エラーコードの違い

TS2322 と混同しやすいエラーコードとの違いを整理します。

エラーコード メッセージ 発生場面
TS2322 Type ‘X’ is not assignable to type ‘Y’ 変数・プロパティ・戻り値への代入
TS2345 Argument of type ‘X’ is not assignable to parameter of type ‘Y’ 関数の引数として渡すとき
TS2339 Property ‘X’ does not exist on type ‘Y’ 存在しないプロパティにアクセス
TS2741 Property ‘X’ is missing in type ‘Y’ 必須プロパティが欠けている
TS2532 Object is possibly ‘undefined’ null/undefinedチェックが不足

覚え方:TS2322は「代入(Assignment)」のエラー、TS2345は「引数(Argument)」のエラーです。エラーメッセージの「assignable to type」と「assignable to parameter of type」の違いに注目しましょう。

関連記事

TypeScriptの型システムをさらに深く学びたい方は、以下の記事もあわせてお読みください。

TypeScript 関連記事