【TypeScript】TS2345の原因と解決方法|Argument of type is not assignable to parameterを完全解説

【TypeScript】TS2345の原因と解決方法|Argument of type is not assignable to parameterを完全解説 TypeScript

TypeScriptで開発していると、TS2345というエラーに遭遇することがあります。これは関数の引数に渡した値の型が、パラメータの型と一致しないときに発生するエラーです。

エラーメッセージは以下のような形式で表示されます。

Argument of type ‘X’ is not assignable to parameter of type ‘Y’. (TS2345)

日本語に訳すと「型’X’の引数を、型’Y’のパラメータに代入できません」という意味です。TypeScriptの型チェックの中でも最も頻出するエラーの一つで、初心者から上級者まで誰もが出会うエラーです。

この記事では、TS2345エラーが発生するすべてのパターンを網羅的に解説し、それぞれの原因正しい解決方法をコード付きで紹介します。

この記事で学べること

  • TS2345エラーの正確な意味とエラーメッセージの読み方
  • プリミティブ型の不一致(string / number / boolean / null / undefined)
  • オブジェクト引数での型不一致パターンと解決方法
  • 配列・タプル引数での型不一致パターンと解決方法
  • コールバック関数の型不一致パターンと解決方法
  • ユニオン型・リテラル型・enum での型不一致
  • ジェネリクス関数での型不一致と制約エラー
  • React / フレームワークでの TS2345 パターン
  • DOM API・標準ライブラリでの TS2345 パターン
  • 型ガード・アサーション・オーバーロードなど正しい解決アプローチ
  • 実務でよくある TS2345 パターン10選
スポンサーリンク
  1. TS2345エラーとは?
    1. エラーメッセージの読み方
    2. TS2345 と TS2322 の違い
    3. なぜ引数の型チェックが重要なのか
  2. 基本的なケースと解決方法
    1. string を number パラメータに渡す
    2. number を string パラメータに渡す
    3. null / undefined を非nullパラメータに渡す
    4. undefined を非undefinedパラメータに渡す
    5. boolean を number/string パラメータに渡す
    6. リテラル型 vs 汎用型の不一致
  3. オブジェクト引数でのTS2345
    1. プロパティの型が違う
    2. 必須プロパティが足りない
    3. 余剰プロパティチェック(変数経由 vs リテラル)
    4. ネストしたオブジェクトの不一致
    5. Partial / Required / Pick / Omit との関係
    6. インデックスシグネチャとオブジェクト引数
  4. 配列引数でのTS2345
    1. number[] を string[] パラメータに渡す
    2. readonly 配列を mutable パラメータに渡す
    3. タプル型パラメータへの配列渡し
    4. スプレッド演算子での引数展開
    5. 配列要素の型がユニオン型になるケース
  5. コールバック関数でのTS2345
    1. コールバックの引数の数が合わない
    2. コールバックの戻り値型の不一致
    3. Array.map / filter / reduce での型不一致
    4. Promise のコールバック
    5. イベントリスナーのコールバック
  6. ユニオン型・リテラル型でのTS2345
    1. string をリテラル型ユニオンパラメータに渡す
    2. ユニオン型の絞り込み不足
    3. as const の活用
    4. enum パラメータへの不正な値
    5. Discriminated Union(判別ユニオン)での型の絞り込み
  7. ジェネリクス関数でのTS2345
    1. 型引数の制約(extends)に合わない
    2. 型推論が期待通りにならない
    3. 複数の型引数での不一致
    4. keyof を使ったジェネリクス制約
  8. React / フレームワークでのTS2345
    1. コンポーネントへのProps渡し
    2. useState のセッター関数
    3. useRef の型不一致
    4. イベントハンドラの引数型
    5. ルーティングライブラリでの型
  9. DOM API・標準ライブラリでのTS2345
    1. addEventListener の引数
    2. setTimeout / setInterval のコールバック
    3. JSON.parse の結果を渡す
    4. Array.from / Map / Set のメソッド引数
  10. 正しい解決アプローチ
    1. 型ガードで絞り込んでから渡す
    2. ジェネリクスで柔軟にする
    3. オーバーロードの活用
    4. as アサーションの適切な使用場面
    5. 関数の型定義を修正する(受け側の問題)
  11. 実務でよくあるTS2345パターン10選
    1. パターン1: APIクライアントへのリクエスト引数
    2. パターン2: フォームデータの送信
    3. パターン3: ライブラリの設定オブジェクト
    4. パターン4: Date コンストラクタ
    5. パターン5: RegExp メソッド
    6. パターン6: Array メソッドチェーン
    7. パターン7: Object.keys / Object.entries の戻り値
    8. パターン8: localStorage / sessionStorage
    9. パターン9: 環境変数(process.env)
    10. パターン10: Promise.all の結果を個別の型付き関数に渡す
  12. まとめ
    1. TS2345 vs TS2322 vs TS2339 比較早見表
    2. 関連記事

TS2345エラーとは?

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

TS2345のエラーメッセージは次の形式で出力されます。

Argument of type ‘string’ is not assignable to parameter of type ‘number’. (TS2345)

このメッセージを分解すると以下のようになります。

パーツ 意味
Argument of type 'string' 渡した引数の型(string)
is not assignable to 代入できない(型に互換性がない)
parameter of type 'number' パラメータの型(number)
(TS2345) TypeScriptのエラーコード

つまり、「あなたが渡した値の型(Argument of type ‘X’)は、関数が期待するパラメータの型(parameter of type ‘Y’)と互換性がありません」ということを伝えています。

TS2345 と TS2322 の違い

TypeScriptには似たエラーとして TS2322 があります。TS2345 との違いを明確にしましょう。

エラーコード 発生場面 メッセージ
TS2345 関数の引数に渡すとき Argument of type ‘X’ is not assignable to parameter of type ‘Y’
TS2322 変数への代入やプロパティ設定時 Type ‘X’ is not assignable to type ‘Y’
TS2345 の例(関数の引数)
function double(n: number): number {
  return n * 2;
}

const value = "hello";
double(value); // TS2345: Argument of type 'string' is not assignable to parameter of type 'number'
TS2322 の例(変数への代入)
let count: number = "hello"; // TS2322: Type 'string' is not assignable to type 'number'

TS2345は「関数呼び出しの引数」に特化したエラーであり、TS2322は「代入全般」のエラーです。解決のアプローチは似ていますが、発生する場面が異なります。

なぜ引数の型チェックが重要なのか

TypeScriptが関数の引数に対して厳密な型チェックを行う理由は、実行時エラーを未然に防ぐためです。

型チェックがない場合の実行時エラー
// JavaScriptの場合(型チェックなし)
function getLength(arr) {
  return arr.length;
}

getLength(42);       // undefined(バグだが動いてしまう)
getLength(null);     // TypeError: Cannot read properties of null
getLength(undefined); // TypeError: Cannot read properties of undefined

TypeScriptでは、これらの問題をコンパイル時に検出して未然に防ぎます。TS2345は「この引数の型は関数が期待する型と違うので、バグの可能性がありますよ」と教えてくれているのです。

ポイント:TS2345エラーは「バグを教えてくれる味方」です。安易にasアサーションやany型で回避せず、なぜ型が合わないのかを理解して正しく修正しましょう。

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

まずはTS2345の最も基本的なパターンから見ていきましょう。プリミティブ型(string, number, boolean など)の不一致です。

string を number パラメータに渡す

最も典型的なTS2345エラーの一つです。

Argument of type ‘string’ is not assignable to parameter of type ‘number’. (TS2345)

エラーが出るコード
function multiply(a: number, b: number): number {
  return a * b;
}

const input = "5";
multiply(input, 10); // TS2345

原因:inputstring 型ですが、multiply 関数の第1引数 anumber 型を期待しています。文字列の "5" と数値の 5 はTypeScriptでは異なる型です。

解決方法1: Number() で変換する
const input = "5";
multiply(Number(input), 10); // OK: Number("5") → 5
解決方法2: parseInt() / parseFloat() で変換する
const input = "5";
multiply(parseInt(input, 10), 10); // OK
解決方法3: 最初から number 型で定義する
const input = 5; // 最初から数値リテラル
multiply(input, 10); // OK

注意:フォーム入力や URL パラメータなど、ブラウザから取得する値は基本的に string 型です。Number()parseInt() で変換する際は、NaN になる可能性も考慮しましょう。

number を string パラメータに渡す

Argument of type ‘number’ is not assignable to parameter of type ‘string’. (TS2345)

エラーが出るコード
function greet(name: string): string {
  return `Hello, ${name}!`;
}

const userId = 12345;
greet(userId); // TS2345
解決方法
// 方法1: String() で変換
greet(String(userId)); // OK: "12345"

// 方法2: テンプレートリテラルで変換
greet(`${userId}`); // OK: "12345"

// 方法3: toString() メソッド
greet(userId.toString()); // OK: "12345"

null / undefined を非nullパラメータに渡す

strictNullChecks が有効な場合(推奨設定)、nullundefined を通常の型のパラメータに渡すとTS2345が発生します。

Argument of type ‘null’ is not assignable to parameter of type ‘string’. (TS2345)

エラーが出るコード
function toUpperCase(text: string): string {
  return text.toUpperCase();
}

const value: string | null = getUserName(); // nullの可能性あり
toUpperCase(value); // TS2345: Argument of type 'string | null' is not assignable to parameter of type 'string'
解決方法1: nullチェック(型ガード)
const value: string | null = getUserName();

if (value !== null) {
  toUpperCase(value); // OK: nullが除外された
}
解決方法2: デフォルト値を使う
const value: string | null = getUserName();
toUpperCase(value ?? "default"); // OK: nullの場合は"default"
解決方法3: 非nullアサーション演算子(!)
const value: string | null = getUserName();
toUpperCase(value!); // OK: nullでないと断言(注意が必要)

注意:非nullアサーション演算子(!)は「この値はnullではない」とTypeScriptに伝えますが、実行時のチェックは行われません。本当にnullでないことが確実な場合にのみ使い、可能ならif文やNullish coalescing(??)を使いましょう。

undefined を非undefinedパラメータに渡す

Argument of type ‘string | undefined’ is not assignable to parameter of type ‘string’.
Type ‘undefined’ is not assignable to type ‘string’. (TS2345)

エラーが出るコード
function processName(name: string): void {
  console.log(name.trim());
}

const map = new Map<string, string>();
const value = map.get("key"); // string | undefined
processName(value); // TS2345

原因:Map.get() の戻り値は V | undefined です。キーが存在しない場合に undefined を返す可能性があるため、string 型のパラメータにはそのまま渡せません。

解決方法
// 方法1: undefinedチェック
const value = map.get("key");
if (value !== undefined) {
  processName(value); // OK
}

// 方法2: デフォルト値
processName(map.get("key") ?? "default"); // OK

// 方法3: Optional chaining + 早期リターン
const val = map.get("key");
if (!val) return;
processName(val); // OK: undefinedが除外された

boolean を number/string パラメータに渡す

Argument of type ‘boolean’ is not assignable to parameter of type ‘number’. (TS2345)

エラーが出るコード
function setCount(count: number): void {
  console.log(`Count: ${count}`);
}

const isActive = true;
setCount(isActive); // TS2345
解決方法
// 方法1: boolean → number に変換
setCount(Number(isActive)); // OK: true → 1, false → 0

// 方法2: 条件式で明示的に数値を渡す
setCount(isActive ? 1 : 0); // OK

リテラル型 vs 汎用型の不一致

TypeScriptでは、リテラル型(具体的な値の型)と汎用型(一般的な型)の間で不一致が起きることがあります。

Argument of type ‘string’ is not assignable to parameter of type ‘”GET” | “POST”‘. (TS2345)

エラーが出るコード
function sendRequest(method: "GET" | "POST"): void {
  console.log(method);
}

const method = "GET"; // TypeScriptは"GET"リテラル型と推論
sendRequest(method); // OK: const宣言なので"GET"リテラル型

let method2 = "GET"; // TypeScriptはstring型と推論
sendRequest(method2); // TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'

原因:let で宣言した変数は再代入可能なため、TypeScriptは広い型(string)と推論します。一方 const で宣言した変数は再代入不可なので、リテラル型("GET")と推論します。

解決方法
// 方法1: const を使う
const method = "GET";
sendRequest(method); // OK

// 方法2: 型注釈を付ける
let method2: "GET" | "POST" = "GET";
sendRequest(method2); // OK

// 方法3: as const でリテラル型に絞り込む
let method3 = "GET" as const;
sendRequest(method3); // OK

ポイント:const 宣言は自動的にリテラル型に推論されますが、let 宣言は再代入の可能性があるため広い型に推論されます。リテラル型が必要な場面では const、型注釈、as const を活用しましょう。

オブジェクト引数でのTS2345

関数にオブジェクトを引数として渡す場面は非常に多く、それだけにTS2345エラーもよく発生します。ここでは、オブジェクト引数での代表的なパターンを詳しく見ていきます。

プロパティの型が違う

オブジェクトのプロパティの型が期待される型と一致しない場合にTS2345が発生します。

Argument of type ‘{ name: string; age: string; }’ is not assignable to parameter of type ‘{ name: string; age: number; }’.
Types of property ‘age’ are incompatible.
Type ‘string’ is not assignable to type ‘number’. (TS2345)

エラーが出るコード
interface User {
  name: string;
  age: number;
}

function registerUser(user: User): void {
  console.log(`${user.name} (${user.age})`);
}

const userData = {
  name: "田中太郎",
  age: "25" // string型(numberではない)
};

registerUser(userData); // TS2345

原因:User インターフェースでは agenumber 型ですが、渡しているオブジェクトの agestring 型("25")です。

解決方法
// 方法1: 正しい型の値を渡す
const userData = {
  name: "田中太郎",
  age: 25 // number型
};
registerUser(userData); // OK

// 方法2: 型注釈を付けて間違いを早期発見
const userData2: User = {
  name: "田中太郎",
  age: 25
};
registerUser(userData2); // OK

ポイント:オブジェクトを関数に渡す場合、変数宣言時に型注釈を付けることで、プロパティの型の間違いを変数宣言時点で検出できます。関数呼び出し時ではなく早い段階でエラーに気づけるので、デバッグが楽になります。

必須プロパティが足りない

Argument of type ‘{ name: string; }’ is not assignable to parameter of type ‘User’.
Property ‘age’ is missing in type ‘{ name: string; }’ but required in type ‘User’. (TS2345)

エラーが出るコード
interface User {
  name: string;
  age: number;
  email: string;
}

function createUser(user: User): void {
  // ...
}

createUser({ name: "田中" }); // TS2345: 'age' と 'email' が足りない
解決方法1: すべての必須プロパティを渡す
createUser({
  name: "田中",
  age: 30,
  email: "tanaka@example.com"
}); // OK
解決方法2: オプショナルプロパティに変更する
interface User {
  name: string;
  age?: number;    // オプショナル
  email?: string;  // オプショナル
}

createUser({ name: "田中" }); // OK
解決方法3: Partial を使う
function updateUser(user: Partial<User>): void {
  // すべてのプロパティがオプショナルになる
}

updateUser({ name: "田中" }); // OK

余剰プロパティチェック(変数経由 vs リテラル)

TypeScriptには余剰プロパティチェックという仕組みがあり、オブジェクトリテラルを直接渡す場合と変数経由で渡す場合で挙動が異なります。

オブジェクトリテラルを直接渡す場合
interface Config {
  host: string;
  port: number;
}

function connect(config: Config): void { }

// オブジェクトリテラルを直接渡す → 余剰プロパティチェックが働く
connect({
  host: "localhost",
  port: 3000,
  timeout: 5000  // エラー: Object literal may only specify known properties
});
変数経由で渡す場合
// 変数経由で渡す → 余剰プロパティチェックが働かない
const options = {
  host: "localhost",
  port: 3000,
  timeout: 5000  // 余剰プロパティだが…
};

connect(options); // OK! 変数経由なのでチェックされない

余剰プロパティチェックの仕組み

  • オブジェクトリテラルを直接渡す:余剰プロパティがあるとエラーになる(タイポ防止のため)
  • 変数経由で渡す:必須プロパティさえあれば余剰プロパティは無視される(構造的部分型)
  • この挙動の違いは意図的なもので、TypeScriptの設計上の判断です

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

オブジェクトが入れ子になっている場合、深い階層でのプロパティ型の不一致もTS2345の原因になります。

Argument of type ‘{ address: { city: string; zip: number; }; }’ is not assignable to parameter of type ‘{ address: { city: string; zip: string; }; }’.
Types of property ‘address’ are incompatible. (TS2345)

エラーが出るコード
interface Address {
  city: string;
  zip: string; // 郵便番号はstring
}

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

function saveProfile(profile: UserProfile): void { }

saveProfile({
  name: "田中",
  address: {
    city: "東京",
    zip: 1000001 // number型(stringが必要)
  }
}); // TS2345
解決方法
saveProfile({
  name: "田中",
  address: {
    city: "東京",
    zip: "100-0001" // string型に修正
  }
}); // OK

Partial / Required / Pick / Omit との関係

TypeScriptのユーティリティ型を使った型変換でも、TS2345が発生するケースがあります。

Partial と Required の使い分け
interface User {
  id: number;
  name: string;
  email: string;
}

// 新規作成: すべて必須
function createUser(user: User): void { }

// 更新: 部分的に更新可能
function updateUser(id: number, updates: Partial<User>): void { }

const partialData: Partial<User> = { name: "田中" };

createUser(partialData); // TS2345: Partial<User> は User に代入できない
updateUser(1, partialData); // OK: Partial<User> を期待している
Pick と Omit の活用
// IDを除いたUser型(新規作成用)
type CreateUserInput = Omit<User, "id">;

function createUserV2(input: CreateUserInput): User {
  return { id: 1, ...input };
}

// OK: id は不要
createUserV2({ name: "田中", email: "tanaka@example.com" });

// 特定のプロパティだけ必要な場合
type UserIdentifier = Pick<User, "id" | "name">;

function identifyUser(user: UserIdentifier): string {
  return `${user.id}: ${user.name}`;
}

identifyUser({ id: 1, name: "田中" }); // OK

インデックスシグネチャとオブジェクト引数

インデックスシグネチャを持つ型のパラメータに対して、異なる値型のプロパティを含むオブジェクトを渡す場合にもTS2345が起きることがあります。

インデックスシグネチャの例
interface StringMap {
  [key: string]: string;
}

function processMap(data: StringMap): void { }

const mixedData = {
  name: "田中",
  age: 25 // number型(StringMapはstringのみ)
};

processMap(mixedData); // TS2345: number は string に代入できない
解決方法
// 方法1: すべて string にする
const data: StringMap = {
  name: "田中",
  age: "25" // string型に変更
};
processMap(data); // OK

// 方法2: Record型を使う
const data2: Record<string, string | number> = {
  name: "田中",
  age: 25
};
// ただし関数の型も変更する必要がある

配列引数でのTS2345

配列を関数の引数として渡す場合にも、さまざまなパターンでTS2345が発生します。ここでは代表的なケースを詳しく解説します。

number[] を string[] パラメータに渡す

Argument of type ‘number[]’ is not assignable to parameter of type ‘string[]’.
Type ‘number’ is not assignable to type ‘string’. (TS2345)

エラーが出るコード
function joinNames(names: string[]): string {
  return names.join(", ");
}

const ids = [1, 2, 3];
joinNames(ids); // TS2345: number[] は string[] に代入できない
解決方法
// 方法1: map で変換する
const ids = [1, 2, 3];
joinNames(ids.map(String)); // OK: ["1", "2", "3"]

// 方法2: 最初から string[] として定義する
const ids2: string[] = ["1", "2", "3"];
joinNames(ids2); // OK

readonly 配列を mutable パラメータに渡す

readonly 配列(読み取り専用配列)を、通常の配列パラメータに渡すとTS2345が発生します。

Argument of type ‘readonly number[]’ is not assignable to parameter of type ‘number[]’.
The type ‘readonly number[]’ is ‘readonly’ and cannot be assigned to the mutable type ‘number[]’. (TS2345)

エラーが出るコード
function sortNumbers(nums: number[]): number[] {
  return nums.sort((a, b) => a - b); // sort は配列を変更する
}

const readonlyNums: readonly number[] = [3, 1, 2];
sortNumbers(readonlyNums); // TS2345

原因:readonly number[] は要素の変更が禁止されている配列ですが、sortNumbers のパラメータ number[] は変更可能な配列を期待しています。readonly配列を変更可能な配列として扱うと、意図せず配列が変更される可能性があるため、TypeScriptはこれを禁止します。

解決方法
// 方法1: パラメータを readonly にする(推奨)
function sortNumbers(nums: readonly number[]): number[] {
  return [...nums].sort((a, b) => a - b); // コピーしてソート
}

// 方法2: スプレッドでコピーを渡す
sortNumbers([...readonlyNums]); // OK: 新しい配列を作成

// 方法3: Array.from でコピーする
sortNumbers(Array.from(readonlyNums)); // OK

ポイント:関数内で配列を変更しないなら、パラメータを readonly にするのがベストプラクティスです。こうすると、readonly 配列も通常の配列も両方受け取れます。

タプル型パラメータへの配列渡し

Argument of type ‘number[]’ is not assignable to parameter of type ‘[number, number]’.
Target requires 2 element(s) but source may have fewer. (TS2345)

エラーが出るコード
function drawLine(start: [number, number], end: [number, number]): void {
  console.log(`from (${start[0]}, ${start[1]}) to (${end[0]}, ${end[1]})`);
}

const point = [10, 20]; // number[] と推論される
drawLine(point, [30, 40]); // TS2345: number[] は [number, number] に代入できない

原因:const point = [10, 20] はTypeScriptによって number[](任意の長さの数値配列)と推論されます。しかし [number, number] は「ちょうど2要素の数値タプル」です。number[] は0要素の可能性もあるため、タプル型に代入できません。

解決方法
// 方法1: as const でタプル型に推論させる
const point = [10, 20] as const; // readonly [10, 20]
drawLine([...point], [30, 40]); // スプレッドでmutableに変換

// 方法2: 明示的にタプル型を指定する
const point2: [number, number] = [10, 20];
drawLine(point2, [30, 40]); // OK

// 方法3: リテラルを直接渡す
drawLine([10, 20], [30, 40]); // OK: リテラルはタプルとして推論

スプレッド演算子での引数展開

スプレッド演算子(...)で配列を関数の引数に展開する場合にもTS2345が発生することがあります。

A spread argument must either have a tuple type or be passed to a rest parameter. (TS2556)

エラーが出るコード
function add(a: number, b: number): number {
  return a + b;
}

const args = [1, 2]; // number[] と推論
add(...args); // エラー
解決方法
// 方法1: as const でタプル型にする
const args = [1, 2] as const;
add(...args); // OK: readonly [1, 2] → 2つの引数に展開

// 方法2: 型注釈でタプル型を指定する
const args2: [number, number] = [1, 2];
add(...args2); // OK

// 方法3: 関数側を可変長引数(rest parameter)にする
function addAll(...nums: number[]): number {
  return nums.reduce((sum, n) => sum + n, 0);
}

const args3 = [1, 2, 3];
addAll(...args3); // OK: rest parameter には配列を展開できる

配列要素の型がユニオン型になるケース

混合配列の型推論
function sumAll(numbers: number[]): number {
  return numbers.reduce((a, b) => a + b, 0);
}

const mixed = [1, "2", 3]; // (string | number)[] と推論
sumAll(mixed); // TS2345: (string | number)[] は number[] に代入できない
解決方法
// 方法1: すべて number にする
const nums = [1, 2, 3];
sumAll(nums); // OK

// 方法2: filter + 型ガードで絞り込む
const filtered = mixed.filter(
  (v): v is number => typeof v === "number"
);
sumAll(filtered); // OK: number[]

// 方法3: map で変換する
const converted = mixed.map(Number);
sumAll(converted); // OK: number[]

コールバック関数でのTS2345

コールバック関数を引数として渡す場面でも、TS2345は頻繁に発生します。特に Array.mapArray.filterPromise、イベントリスナーなどでよく見られるパターンです。

コールバックの引数の数が合わない

TypeScriptでは、コールバック関数の引数の数が少ない分には許容されますが、多い場合はエラーになります。

コールバック引数の互換性
type Callback = (a: number, b: number) => void;

function execute(cb: Callback): void {
  cb(1, 2);
}

// OK: 引数が少ないのは許容される
execute((a) => console.log(a));
execute(() => console.log("done"));

// エラー: 引数の型が違う
execute((a: string) => console.log(a)); // TS2345

コールバックの戻り値型の不一致

Argument of type ‘(item: string) => string’ is not assignable to parameter of type ‘(item: string) => number’.
Type ‘string’ is not assignable to type ‘number’. (TS2345)

エラーが出るコード
function processItems(items: string[], transform: (item: string) => number): number[] {
  return items.map(transform);
}

// コールバックが string を返しているが、number が期待されている
processItems(["a", "b"], (item) => item.toUpperCase()); // TS2345
解決方法
// 方法1: 正しい戻り値を返す
processItems(["a", "b"], (item) => item.length); // OK: number を返す

// 方法2: 関数の型定義を修正する
function processItems2<T>(items: string[], transform: (item: string) => T): T[] {
  return items.map(transform);
}

processItems2(["a", "b"], (item) => item.toUpperCase()); // OK: string[]

Array.map / filter / reduce での型不一致

配列メソッドのコールバックでTS2345が発生するケースは非常に多いです。

Array.filter での型の絞り込み
const items: (string | null)[] = ["a", null, "b", null];

// filter の結果は (string | null)[] のまま
const strings = items.filter(item => item !== null);
// strings は (string | null)[] と推論される

function processStrings(strs: string[]): void { }
processStrings(strings); // TS2345: (string | null)[] は string[] に代入できない
解決方法: 型ガード付き filter
// 方法1: 型述語(Type Predicate)を使う
const strings = items.filter(
  (item): item is string => item !== null
);
processStrings(strings); // OK: string[]

// 方法2: 汎用的な nonNullable フィルター
function isNonNull<T>(value: T | null | undefined): value is T {
  return value != null;
}

const strings2 = items.filter(isNonNull);
processStrings(strings2); // OK: string[]

ポイント:Array.filter は通常、型の絞り込みを行いません。型の絞り込みを行うには、型述語(Type Predicate)を使ったコールバック関数が必要です。(item): item is string => ... の形式で書きます。

Promise のコールバック

Promise.then での型不一致
function fetchUser(): Promise<{ name: string; age: number }> {
  return fetch("/api/user").then(res => res.json());
}

function displayName(name: string): void {
  console.log(name);
}

// エラー: User オブジェクトを string パラメータに渡している
fetchUser().then(displayName); // TS2345
解決方法
// 方法1: ラップして正しいプロパティを渡す
fetchUser().then(user => displayName(user.name)); // OK

// 方法2: User型を受け取る関数を使う
function displayUser(user: { name: string; age: number }): void {
  console.log(user.name);
}
fetchUser().then(displayUser); // OK

イベントリスナーのコールバック

Argument of type ‘(e: MouseEvent) => void’ is not assignable to parameter of type ‘(e: KeyboardEvent) => void’. (TS2345)

イベントハンドラの型不一致
type KeyHandler = (e: KeyboardEvent) => void;

function onKeyPress(handler: KeyHandler): void {
  document.addEventListener("keypress", handler);
}

// MouseEvent のハンドラを KeyHandler に渡している
const clickHandler = (e: MouseEvent) => {
  console.log(e.clientX, e.clientY);
};

onKeyPress(clickHandler); // TS2345
解決方法
// 正しいイベント型のハンドラを使う
const keyHandler = (e: KeyboardEvent) => {
  console.log(e.key);
};

onKeyPress(keyHandler); // OK

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

ユニオン型やリテラル型は TypeScript の強力な機能ですが、型の不一致によるTS2345エラーの原因にもなります。ここでは代表的なパターンを解説します。

string をリテラル型ユニオンパラメータに渡す

最もよくあるパターンの一つです。一般的な string 型を、特定の文字列リテラルのユニオン型に渡そうとするとエラーになります。

Argument of type ‘string’ is not assignable to parameter of type ‘”sm” | “md” | “lg”‘. (TS2345)

エラーが出るコード
type Size = "sm" | "md" | "lg";

function setSize(size: Size): void {
  console.log(`Size: ${size}`);
}

const userInput: string = getFromInput(); // string型
setSize(userInput); // TS2345: string は "sm" | "md" | "lg" に代入できない

原因:string 型は任意の文字列を表しますが、"sm" | "md" | "lg" は3つの特定の文字列のみを許容します。TypeScriptは string がこの3つの値のいずれかであることを保証できないため、エラーになります。

解決方法
// 方法1: 型ガードで検証する(最も安全)
function isSize(value: string): value is Size {
  return ["sm", "md", "lg"].includes(value);
}

if (isSize(userInput)) {
  setSize(userInput); // OK: userInput は Size 型に絞り込まれた
}

// 方法2: 型アサーション(値が正しいと確信できる場合のみ)
setSize(userInput as Size); // OK だが安全ではない

// 方法3: デフォルト値付きのバリデーション
const validSizes: Size[] = ["sm", "md", "lg"];
const size: Size = validSizes.includes(userInput as Size)
  ? (userInput as Size)
  : "md"; // デフォルト値
setSize(size); // OK

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

ユニオン型の変数をそのまま渡すと、ユニオン型のすべての可能性が考慮されるため、単一型のパラメータには渡せません。

Argument of type ‘string | number’ is not assignable to parameter of type ‘string’.
Type ‘number’ is not assignable to type ‘string’. (TS2345)

エラーが出るコード
function toUpperCase(text: string): string {
  return text.toUpperCase();
}

const value: string | number = getInput();
toUpperCase(value); // TS2345: string | number は string に代入できない
解決方法: 型を絞り込んでから渡す
// 方法1: typeof による型ガード
if (typeof value === "string") {
  toUpperCase(value); // OK: string に絞り込まれた
}

// 方法2: すべてのケースを処理する
if (typeof value === "string") {
  toUpperCase(value);
} else {
  toUpperCase(String(value)); // number → string に変換
}

// 方法3: String() で統一して変換
toUpperCase(String(value)); // OK: String(123) → "123"

as const の活用

as const はTypeScriptで値をリテラル型として扱うための重要なテクニックです。特にオブジェクトや配列のプロパティをリテラル型として保持したい場合に有用です。

as const が必要なケース
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";

function request(url: string, method: HttpMethod): void { }

// as const なし → method は string 型
const config = {
  url: "https://api.example.com",
  method: "GET"
};
request(config.url, config.method); // TS2345: string は HttpMethod に代入できない

// as const あり → method は "GET" リテラル型
const config2 = {
  url: "https://api.example.com",
  method: "GET"
} as const;
request(config2.url, config2.method); // OK: "GET" リテラル型

as const の効果

  • オブジェクトのすべてのプロパティが readonly になる
  • 文字列・数値リテラルがそのまま型になる("GET"string ではなく "GET"
  • 配列がタプル型になる([1, 2]number[] ではなく readonly [1, 2]

enum パラメータへの不正な値

Argument of type ‘string’ is not assignable to parameter of type ‘Color’. (TS2345)

エラーが出るコード
enum Color {
  Red = "RED",
  Green = "GREEN",
  Blue = "BLUE"
}

function paint(color: Color): void {
  console.log(`Painting with ${color}`);
}

paint("RED"); // TS2345: 'string' is not assignable to type 'Color'

原因:文字列 "RED" と enum値 Color.Red(= "RED")は、値としては同じですが型としては異なります。TypeScriptのenumは名前空間として機能するため、直接文字列は渡せません。

解決方法
// 方法1: enum メンバーを使う(推奨)
paint(Color.Red); // OK

// 方法2: 文字列からenumに変換する
const input = "RED";
if (Object.values(Color).includes(input as Color)) {
  paint(input as Color); // OK: バリデーション済み
}

// 方法3: enum の代わりにユニオン型を使う
type ColorType = "RED" | "GREEN" | "BLUE";
function paintV2(color: ColorType): void { }
paintV2("RED"); // OK: 文字列リテラルを直接渡せる

Discriminated Union(判別ユニオン)での型の絞り込み

判別ユニオンの型絞り込み
type Shape =
  | { kind: "circle"; radius: number }
  | { kind: "square"; side: number };

function getCircleArea(circle: { kind: "circle"; radius: number }): number {
  return Math.PI * circle.radius ** 2;
}

const shape: Shape = { kind: "circle", radius: 5 };
getCircleArea(shape); // TS2345: Shape は { kind: "circle"; ... } に代入できない

// 解決: kind プロパティで判別する
if (shape.kind === "circle") {
  getCircleArea(shape); // OK: circle に絞り込まれた
}

ジェネリクス関数でのTS2345

ジェネリクス(型パラメータ)を使った関数では、型パラメータの制約条件や推論の結果によってTS2345が発生することがあります。

型引数の制約(extends)に合わない

Argument of type ‘number’ is not assignable to parameter of type ‘{ length: number; }’.
Type ‘number’ is not assignable to type ‘{ length: number; }’. (TS2345)

エラーが出るコード
function getLength<T extends { length: number }>(arg: T): number {
  return arg.length;
}

getLength("hello");      // OK: string は length を持つ
getLength([1, 2, 3]);    // OK: 配列は length を持つ
getLength(42);           // TS2345: number は { length: number } を持たない
getLength(true);         // TS2345: boolean は { length: number } を持たない

原因:型パラメータ Textends { length: number } という制約があり、length プロパティを持つ型のみ受け付けます。numberbooleanlength プロパティを持たないため、制約を満たしません。

解決方法
// 方法1: 制約を満たす値を渡す
getLength("hello");  // OK: string.length
getLength([1, 2]);   // OK: Array.length
getLength({ length: 5 }); // OK: { length: number }

// 方法2: 配列にラップする
getLength([42]); // OK: 配列として渡す

// 方法3: 文字列に変換する
getLength(String(42)); // OK: "42".length → 2

型推論が期待通りにならない

TypeScriptの型推論は強力ですが、ジェネリクスとの組み合わせで意図しない推論結果になることがあります。

型推論の問題
function merge<T>(target: T, source: Partial<T>): T {
  return { ...target, ...source };
}

interface User {
  name: string;
  age: number;
}

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

// T が User と推論される → source は Partial<User>
merge(user, { name: "佐藤" }); // OK
merge(user, { name: "佐藤", role: "admin" }); // エラー: role は User に存在しない
解決方法: 型を広げる or 明示する
// 方法1: 既存のプロパティのみ渡す
merge(user, { name: "佐藤" }); // OK

// 方法2: 型定義を拡張する
interface UserWithRole extends User {
  role?: string;
}

const userWithRole: UserWithRole = { name: "田中", age: 30 };
merge(userWithRole, { role: "admin" }); // OK

複数の型引数での不一致

複数の型引数が絡むエラー
function transform<T, U>(input: T, fn: (value: T) => U): U {
  return fn(input);
}

// T が number と推論 → fn は (value: number) => U
transform(42, (value: string) => value.length); // TS2345
// string は number に代入できない
解決方法
// 方法1: コールバックの引数型を合わせる
transform(42, (value) => value * 2); // OK: T=number, U=number

// 方法2: 型引数を明示する
transform<string, number>("hello", (value) => value.length);
// OK: T=string, U=number

// 方法3: 入力値の型を変える
transform("42", (value) => value.length); // OK: T=string

keyof を使ったジェネリクス制約

keyof 制約でのエラー
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}

interface User {
  name: string;
  age: number;
}

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

getProperty(user, "name");    // OK: "name" は keyof User
getProperty(user, "age");     // OK: "age" は keyof User
getProperty(user, "email");   // TS2345: "email" は keyof User に代入できない

const key: string = "name";
getProperty(user, key);      // TS2345: string は keyof User に代入できない
解決方法
// 方法1: リテラル文字列を直接渡す
getProperty(user, "name"); // OK

// 方法2: 型を正しく指定する
const key: keyof User = "name";
getProperty(user, key); // OK

// 方法3: 動的なキーの場合は型ガード
function isUserKey(key: string): key is keyof User {
  return ["name", "age"].includes(key);
}

const dynamicKey = "name";
if (isUserKey(dynamicKey)) {
  getProperty(user, dynamicKey); // OK
}

React / フレームワークでのTS2345

Reactなどのフレームワークを使った開発では、コンポーネントのProps、Hooks、イベントハンドラなどで頻繁にTS2345が発生します。ここではReactに焦点を当てて解説しますが、他のフレームワーク(Vue、Angularなど)でも同様のパターンが当てはまります。

コンポーネントへのProps渡し

Argument of type ‘{ title: string; count: string; }’ is not assignable to parameter of type ‘{ title: string; count: number; }’.
Types of property ‘count’ are incompatible. (TS2345)

エラーが出るコード(React)
interface BadgeProps {
  label: string;
  count: number;
  variant?: "primary" | "secondary";
}

function Badge({ label, count, variant = "primary" }: BadgeProps) {
  return <span>{label}: {count}</span>;
}

// count に文字列を渡している
<Badge label="通知" count="5" />; // TS2345: string は number に代入できない
解決方法
// 方法1: 正しい型の値を渡す
<Badge label="通知" count={5} />; // OK: JSX式で数値を渡す

// 方法2: 変数の場合は Number() で変換
const countStr = "5";
<Badge label="通知" count={Number(countStr)} />; // OK

注意:JSXでPropsに文字列以外の値を渡す場合は、必ず波括弧 {} で囲みます。count="5" は文字列 "5" ですが、count={5} は数値 5 です。

useState のセッター関数

Argument of type ‘string’ is not assignable to parameter of type ‘SetStateAction<number>’. (TS2345)

エラーが出るコード
const [count, setCount] = useState(0); // count は number

setCount("5"); // TS2345: string は SetStateAction<number> に代入できない
setCount(null); // TS2345: null は SetStateAction<number> に代入できない
解決方法
// 方法1: 正しい型の値を渡す
setCount(5); // OK
setCount(prev => prev + 1); // OK: 関数形式

// 方法2: null を許容したい場合は初期値の型を変更
const [count2, setCount2] = useState<number | null>(0);
setCount2(null); // OK
setCount2(5); // OK

// 方法3: 文字列入力を変換する
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  setCount(Number(e.target.value)); // string → number
};

useRef の型不一致

Argument of type ‘MutableRefObject<HTMLDivElement | null>’ is not assignable to parameter of type ‘RefObject<HTMLInputElement>’. (TS2345)

エラーが出るコード
const inputRef = useRef<HTMLDivElement>(null);

// HTMLInputElement のメソッドを使おうとしている
function focusInput(ref: React.RefObject<HTMLInputElement>) {
  ref.current?.focus();
}

focusInput(inputRef); // TS2345: HTMLDivElement は HTMLInputElement に代入できない
解決方法
// 正しいHTML要素型を指定する
const inputRef = useRef<HTMLInputElement>(null);
focusInput(inputRef); // OK

// よく使う HTML 要素型:
// HTMLInputElement  - <input>
// HTMLDivElement    - <div>
// HTMLButtonElement - <button>
// HTMLFormElement   - <form>
// HTMLTextAreaElement - <textarea>
// HTMLSelectElement - <select>
// HTMLAnchorElement - <a>
// HTMLImageElement  - <img>

イベントハンドラの引数型

React イベントハンドラの型不一致
// ChangeEvent を MouseEvent パラメータに渡す
function handleClick(e: React.MouseEvent<HTMLButtonElement>) {
  console.log(e.clientX);
}

const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {
  console.log(e.target.value);
};

// エラー: ChangeEvent を MouseEvent に渡せない
<button onClick={handleChange}>Click</button>; // TS2345
解決方法: 正しいイベント型を使う
// 正しいイベントハンドラ型の対応表:
// onClick    → React.MouseEvent<HTMLElement>
// onChange   → React.ChangeEvent<HTMLElement>
// onSubmit   → React.FormEvent<HTMLFormElement>
// onKeyDown  → React.KeyboardEvent<HTMLElement>
// onFocus    → React.FocusEvent<HTMLElement>
// onDrag     → React.DragEvent<HTMLElement>

// 正しい対応:
<button onClick={handleClick}>Click</button>; // OK
<input onChange={handleChange} />; // OK
JSX属性 React イベント型 主な用途
onClick React.MouseEvent ボタン・リンクのクリック
onChange React.ChangeEvent input/select の値変更
onSubmit React.FormEvent フォーム送信
onKeyDown React.KeyboardEvent キーボード入力
onFocus / onBlur React.FocusEvent フォーカス制御
onDrag / onDrop React.DragEvent ドラッグ&ドロップ

ルーティングライブラリでの型

React Router や Next.js などのルーティングライブラリでもTS2345は発生します。

React Router でのパス型
// useParams() の戻り値は Record<string, string | undefined>
const { id } = useParams(); // id: string | undefined

function fetchUser(userId: number): Promise<User> { ... }

fetchUser(id); // TS2345: string | undefined は number に代入できない
解決方法
const { id } = useParams();

// 方法1: undefined チェック + Number変換
if (id) {
  fetchUser(Number(id)); // OK
}

// 方法2: パース用のヘルパー関数
function parseId(id: string | undefined): number | null {
  if (!id) return null;
  const parsed = Number(id);
  return Number.isNaN(parsed) ? null : parsed;
}

const userId = parseId(id);
if (userId !== null) {
  fetchUser(userId); // OK
}

DOM API・標準ライブラリでのTS2345

ブラウザのDOM APIやJavaScriptの標準ライブラリを使う場面でも、TS2345は頻繁に発生します。特に querySelectorJSON.parse など、戻り値の型が曖昧な関数と組み合わせるときに起きやすいです。

addEventListener の引数

addEventListener でのTS2345
const button = document.querySelector("button");
// button は HTMLButtonElement | null

function handleClick(e: MouseEvent): void {
  console.log(e.clientX);
}

// null の可能性があるためエラー
button.addEventListener("click", handleClick); // button は null かもしれない
解決方法
// 方法1: null チェック
const button = document.querySelector("button");
if (button) {
  button.addEventListener("click", handleClick); // OK
}

// 方法2: Optional chaining
button?.addEventListener("click", handleClick); // OK

// 方法3: 非nullアサーション(要素が確実に存在する場合)
const button2 = document.querySelector("button")!;
button2.addEventListener("click", handleClick); // OK

setTimeout / setInterval のコールバック

setTimeout の戻り値の型
// Node.js 環境では setTimeout は NodeJS.Timeout を返す
// ブラウザ環境では number を返す

const timerId: number = setTimeout(() => {}, 1000);
// Node.js環境: TS2345: Timeout は number に代入できない
解決方法
// 方法1: ReturnType を使う(推奨)
const timerId: ReturnType<typeof setTimeout> = setTimeout(() => {}, 1000);

// 方法2: window.setTimeout を使う(ブラウザ環境)
const timerId2: number = window.setTimeout(() => {}, 1000);

// 方法3: NodeJS.Timeout を使う(Node.js環境)
const timerId3: NodeJS.Timeout = setTimeout(() => {}, 1000);

JSON.parse の結果を渡す

JSON.parse() の戻り値は any 型なので、そのまま型付き関数に渡すとTS2345は発生しません。ただし、strictNullChecksnoImplicitAny の設定や、戻り値を unknown に設定する場合はエラーになります。

JSON.parse を安全に使う
interface User {
  name: string;
  age: number;
}

function processUser(user: User): void {
  console.log(user.name);
}

// any 型なのでエラーにならないが、安全ではない
const data = JSON.parse('{"name": "田中"}');
processUser(data); // OK だが age がないのでバグ!

// より安全なアプローチ: 型チェック関数
function isUser(value: unknown): value is User {
  return (
    typeof value === "object" &&
    value !== null &&
    "name" in value &&
    "age" in value &&
    typeof (value as User).name === "string" &&
    typeof (value as User).age === "number"
  );
}

const parsed: unknown = JSON.parse(jsonString);
if (isUser(parsed)) {
  processUser(parsed); // OK: 安全に型が保証される
}

Array.from / Map / Set のメソッド引数

Map / Set での型不一致
const numberSet = new Set<number>([1, 2, 3]);

numberSet.add("4"); // TS2345: string は number に代入できない
numberSet.has("3"); // TS2345: string は number に代入できない
numberSet.delete("1"); // TS2345: string は number に代入できない

const userMap = new Map<number, string>();
userMap.set("1", "田中"); // TS2345: string は number に代入できない
userMap.get("1");           // TS2345: string は number に代入できない
解決方法
// 正しい型の値を使う
numberSet.add(4);    // OK
numberSet.has(3);    // OK
numberSet.delete(1); // OK

userMap.set(1, "田中"); // OK
userMap.get(1);           // OK

// 文字列のキーを使いたい場合は Map の型を変更
const userMap2 = new Map<string, string>();
userMap2.set("1", "田中"); // OK

正しい解決アプローチ

TS2345エラーに遭遇したとき、安易に anyas で回避するのではなく、正しいアプローチで解決することが重要です。ここでは、状況に応じた適切な解決方法を体系的にまとめます。

型ガードで絞り込んでから渡す

最も安全で推奨される方法です。実行時のチェックとTypeScriptの型推論を連動させます。

型ガードのパターン一覧
function processValue(value: string): void { }

const input: string | number | null | undefined = getInput();

// 1. typeof ガード
if (typeof input === "string") {
  processValue(input); // OK: string
}

// 2. null/undefined チェック
if (input != null && typeof input === "string") {
  processValue(input); // OK
}

// 3. Truthyチェック(空文字も除外される点に注意)
if (input && typeof input === "string") {
  processValue(input); // OK
}

// 4. instanceof ガード
function processDate(date: Date): void { }

const maybeDate: Date | string = getValue();
if (maybeDate instanceof Date) {
  processDate(maybeDate); // OK: Date
}

// 5. in ガード
function processWithName(obj: { name: string }): void { }

const obj: unknown = getObj();
if (typeof obj === "object" && obj !== null && "name" in obj) {
  processWithName(obj as { name: string }); // OK
}

// 6. ユーザー定義型ガード(カスタム型ガード)
function isString(value: unknown): value is string {
  return typeof value === "string";
}

if (isString(input)) {
  processValue(input); // OK
}

ジェネリクスで柔軟にする

関数の引数型が固定的すぎてTS2345が発生する場合、ジェネリクスを使うことで柔軟性を持たせることができます。

ジェネリクスによる柔軟化
// Before: 型が固定的
function first(arr: string[]): string {
  return arr[0];
}
first([1, 2, 3]); // TS2345: number[] は string[] に代入できない

// After: ジェネリクスで柔軟に
function first<T>(arr: T[]): T {
  return arr[0];
}
first([1, 2, 3]); // OK: T=number → number
first(["a", "b"]); // OK: T=string → string

オーバーロードの活用

同じ関数名で異なる引数パターンを受け付けたい場合は、オーバーロードが有効です。

関数オーバーロード
// オーバーロードシグネチャ
function format(value: number): string;
function format(value: Date): string;
function format(value: string): string;

// 実装シグネチャ
function format(value: number | Date | string): string {
  if (typeof value === "number") {
    return value.toFixed(2);
  } else if (value instanceof Date) {
    return value.toISOString();
  } else {
    return value.trim();
  }
}

format(42);            // OK: "42.00"
format(new Date());    // OK: ISO文字列
format(" hello ");    // OK: "hello"
format(true);          // TS2345: boolean はどのオーバーロードにも合わない

as アサーションの適切な使用場面

型アサーション(as)はTS2345の解決に使えますが、乱用は危険です。適切な使用場面を理解しましょう。

使用場面 安全性 推奨度
TypeScriptの推論よりも開発者が型を詳しく知っている OK
バリデーション済みのデータに型を付ける 条件付きOK
ライブラリの型定義が不正確 条件付きOK
エラーを黙らせるためだけに使う 非推奨
as any でエラーを完全に無視する なし 禁止
as の適切な使用例
// 適切: querySelector の結果を正しい要素型に絞り込む
const input = document.querySelector("#email") as HTMLInputElement;
input.value; // OK(要素が存在することを確認済みの場合)

// 適切: APIレスポンスの型を指定(スキーマ検証済み)
const response = await fetch("/api/users");
const users = (await response.json()) as User[];

// 不適切: 型を完全に無視する
processNumber("hello" as any); // 危険!実行時エラーの原因

注意:as any はTypeScriptの型チェックを完全に無効化します。コンパイルエラーは消えますが、実行時エラーのリスクが高まります。as any を使う前に、まず型ガードやジェネリクスで解決できないか検討しましょう。

関数の型定義を修正する(受け側の問題)

TS2345は「渡す側」の問題だけでなく、「受ける側(関数定義)」の型が狭すぎる場合もあります。その場合は関数の型定義を見直しましょう。

受け側の型を修正する
// Before: パラメータが狭すぎる
function log(message: string): void {
  console.log(message);
}
log(42); // TS2345

// After: 複数の型を受け入れる
function log(message: string | number | boolean): void {
  console.log(String(message));
}
log(42); // OK

// さらに柔軟: unknown を使う
function log(message: unknown): void {
  console.log(String(message));
}
log(42);     // OK
log("hi");   // OK
log(null);   // OK

TS2345 解決フローチャート

  • Step 1: エラーメッセージの ‘X’ と ‘Y’ を確認する
  • Step 2: 渡す値の型(X)と期待される型(Y)の違いを理解する
  • Step 3-A: 渡す値を変換・変更できる → 型変換、型ガードで解決
  • Step 3-B: 関数定義を変更できる → パラメータ型を広げる、ジェネリクス化
  • Step 3-C: どちらも変更できない → as アサーション(最終手段)

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

ここでは、実際の開発現場で頻出するTS2345のパターンを10個厳選して紹介します。すぐにコピー&ペーストして使える解決コード付きです。

パターン1: APIクライアントへのリクエスト引数

APIリクエストの型不一致
interface CreatePostRequest {
  title: string;
  body: string;
  publishedAt: Date;
}

async function createPost(data: CreatePostRequest): Promise<void> { }

// フォームデータ(日付がstring型)
const formData = {
  title: "記事タイトル",
  body: "本文",
  publishedAt: "2024-01-15" // string(Dateではない)
};

createPost(formData); // TS2345
解決方法
createPost({
  ...formData,
  publishedAt: new Date(formData.publishedAt) // string → Date
}); // OK

パターン2: フォームデータの送信

フォームの値はすべて string
interface UserRegistration {
  name: string;
  age: number;
  isAdmin: boolean;
}

function register(data: UserRegistration): void { }

// HTMLフォームの値はすべてstring
const form = document.querySelector("form")!;
const formData = new FormData(form);

register({
  name: formData.get("name"),     // FormDataEntryValue | null
  age: formData.get("age"),       // FormDataEntryValue | null
  isAdmin: formData.get("admin")  // FormDataEntryValue | null
}); // TS2345(すべての型が不一致)
解決方法: バリデーション + 型変換
const name = formData.get("name");
const age = formData.get("age");
const admin = formData.get("admin");

if (typeof name === "string" && typeof age === "string") {
  register({
    name: name,
    age: Number(age),
    isAdmin: admin === "on"  // checkbox → boolean
  }); // OK
}

パターン3: ライブラリの設定オブジェクト

ライブラリの設定で as const が必要
// fetch APIの例
const options = {
  method: "POST",       // string と推論される
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ data: "test" })
};

// fetch の RequestInit は method: string を受け付けるので
// この場合はOKだが、より厳密なAPIクライアントではエラーになる

// 解決: as const で型を保持する
const options2 = {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({ data: "test" })
} as const;

パターン4: Date コンストラクタ

Date コンストラクタへの型不一致
function formatDate(date: Date): string {
  return date.toLocaleDateString("ja-JP");
}

const timestamp = "2024-01-15T00:00:00Z"; // string
formatDate(timestamp); // TS2345: string は Date に代入できない

const unixTime = 1705276800000; // number
formatDate(unixTime); // TS2345: number は Date に代入できない
解決方法
// new Date() で変換する
formatDate(new Date(timestamp)); // OK: string → Date
formatDate(new Date(unixTime));  // OK: number → Date

// または関数を柔軟にする
function formatDate(date: Date | string | number): string {
  const d = date instanceof Date ? date : new Date(date);
  return d.toLocaleDateString("ja-JP");
}

パターン5: RegExp メソッド

正規表現の結果を渡す
function processMatch(match: string): void {
  console.log(match);
}

const result = "hello 123".match(/\d+/);
// result は RegExpMatchArray | null

processMatch(result); // TS2345: RegExpMatchArray | null は string に代入できない

// 解決
if (result) {
  processMatch(result[0]); // OK: 最初のマッチを string で渡す
}

パターン6: Array メソッドチェーン

find の結果を渡す
interface User {
  id: number;
  name: string;
}

function displayUser(user: User): void { }

const users: User[] = [{ id: 1, name: "田中" }];
const found = users.find(u => u.id === 1); // User | undefined

displayUser(found); // TS2345: User | undefined は User に代入できない

// 解決
if (found) {
  displayUser(found); // OK
}

// または早期リターン
const user = users.find(u => u.id === 1);
if (!user) throw new Error("User not found");
displayUser(user); // OK: undefined が除外された

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

Object.keys は string[] を返す
interface Config {
  host: string;
  port: number;
}

function getConfigValue(config: Config, key: keyof Config): string | number {
  return config[key];
}

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

// Object.keys は string[] を返す(keyof Config ではない)
Object.keys(config).forEach(key => {
  getConfigValue(config, key); // TS2345: string は keyof Config に代入できない
});
解決方法
// 方法1: キャストする
(Object.keys(config) as (keyof Config)[]).forEach(key => {
  getConfigValue(config, key); // OK
});

// 方法2: 型安全な keys ヘルパー関数
function typedKeys<T extends object>(obj: T): (keyof T)[] {
  return Object.keys(obj) as (keyof T)[];
}

typedKeys(config).forEach(key => {
  getConfigValue(config, key); // OK
});

パターン8: localStorage / sessionStorage

localStorage の値は string | null
function setTheme(theme: "light" | "dark"): void { }

const savedTheme = localStorage.getItem("theme");
// savedTheme は string | null

setTheme(savedTheme); // TS2345: string | null は "light" | "dark" に代入できない

// 解決: バリデーション付きのヘルパー
type Theme = "light" | "dark";

function getStoredTheme(): Theme {
  const saved = localStorage.getItem("theme");
  if (saved === "light" || saved === "dark") {
    return saved;
  }
  return "light"; // デフォルト値
}

setTheme(getStoredTheme()); // OK

パターン9: 環境変数(process.env)

process.env は string | undefined
function connectDB(connectionString: string): void { }

connectDB(process.env.DATABASE_URL); // TS2345: string | undefined は string に代入できない

// 解決方法
const dbUrl = process.env.DATABASE_URL;
if (!dbUrl) {
  throw new Error("DATABASE_URL is not set");
}
connectDB(dbUrl); // OK: undefined が除外された

// または ?? でデフォルト値を指定
connectDB(process.env.DATABASE_URL ?? "postgresql://localhost:5432/dev");

パターン10: Promise.all の結果を個別の型付き関数に渡す

Promise.all の分割代入
async function fetchData() {
  const [users, posts] = await Promise.all([
    fetch("/api/users").then(r => r.json()),  // any
    fetch("/api/posts").then(r => r.json())   // any
  ]);

  // any型なのでエラーにならないが型安全ではない
  // 型安全にするためには明示的に型を付ける
}

// 型安全なアプローチ
async function fetchDataTyped() {
  const [users, posts] = await Promise.all([
    fetch("/api/users").then(r => r.json()) as Promise<User[]>,
    fetch("/api/posts").then(r => r.json()) as Promise<Post[]>
  ]);

  processUsers(users);  // OK: User[]
  processPosts(posts);  // OK: Post[]
}

まとめ

TS2345は「関数の引数に渡した値の型が、パラメータの型と一致しない」ときに発生するエラーです。TypeScriptで最も頻出するエラーの一つですが、エラーメッセージを正しく読み解き、適切なアプローチで修正すれば必ず解決できます。

TS2345 解決の要点

  • エラーメッセージを読む:’X’ と ‘Y’ から、何の型が何の型に合わないのかを把握する
  • 型ガードを使うtypeofinstanceof、型述語で型を絞り込んでから渡す
  • 型変換を行うNumber()String()new Date() などで正しい型に変換する
  • as const を活用する:リテラル型が必要な場面で型の拡大を防ぐ
  • ジェネリクスで柔軟にする:関数が特定の型に縛られている場合はジェネリクス化を検討する
  • 受け側の型を見直す:パラメータの型が狭すぎる場合は、ユニオン型やジェネリクスで広げる
  • as アサーションは最終手段:型ガードやジェネリクスで解決できない場合のみ使用する

TS2345 vs TS2322 vs TS2339 比較早見表

TypeScriptの代表的な型エラー3つの違いを一覧にまとめます。

エラーコード メッセージ 発生場面 典型例
TS2345 Argument of type ‘X’ is not assignable to parameter of type ‘Y’ 関数の引数 fn("hello")(fn は number を期待)
TS2322 Type ‘X’ is not assignable to type ‘Y’ 変数への代入・戻り値 let x: number = "hello"
TS2339 Property ‘X’ does not exist on type ‘Y’ プロパティアクセス obj.unknownProp

関連記事

TypeScriptの型システムや他のエラーについて、さらに詳しく学びたい方は以下の記事もご覧ください。

関連記事