エラーの内容
プロパティにアクセスすると、TypeScriptがすぐに赤い下線を引きます:
const user = {};
console.log(user.name); // Property 'name' does not exist on type '{}'. ts(2339)
関数の戻り値、APIレスポンス、Reactのstateでも同様のエラーが発生します:
function getUser() {
return {};
}
const user = getUser();
console.log(user.name); // ts(2339)
TypeScriptは型を{}(プロパティを持たない空のオブジェクト)と推論しました。nameが存在するという根拠がないため、コンパイルできません。
原因
TypeScriptは構造的型付けを採用しています。型には明示的に宣言したプロパティのみが含まれ、何も仮定されません。{}と書いたり、アノテーションなしで素のオブジェクトを返したりすると、TypeScriptは推論できる最も狭い型を選択します。その型へのプロパティアクセスはすべてts(2339)を引き起こします。
よくある原因:
{}で初期化し、後からプロパティを追加するオブジェクト- 汎用オブジェクト(
object、{}、Record<string, unknown>)を返すと型付けされた関数 anyとして型付けされ、どこかで{}に絞り込まれたAPI/JSONレスポンス- インターフェース定義の欠如または誤り
- 条件付きで追加されたが型に反映されていないプロパティ
修正方法1:インターフェースまたは型を定義する
まずここから始めましょう。オブジェクトの形状を明示的に宣言する方法が、スケールする修正方法です:
interface User {
name: string;
age?: number; // オプション
}
const user: User = { name: 'Alice' };
console.log(user.name); // OK
関数の戻り値にも、戻り値の型をアノテーションしましょう:
function getUser(): User {
return { name: 'Alice' };
}
const user = getUser();
console.log(user.name); // OK
修正方法2:型アサーション
データの形状はわかっているが、TypeScriptにはわからない場合は、asを使って伝えましょう:
const raw = JSON.parse(response) as { name: string };
console.log(raw.name); // OK
オブジェクトを段階的に構築する場合:
const user = {} as User;
user.name = 'Alice';
console.log(user.name); // OK
**注意:**アサーションは型チェックを完全にバイパスします。実際のデータが一致しない場合、警告なしにランタイムエラーが発生します。形状が確実にわかっている場合にのみ使用してください。
修正方法3:インデックスシグネチャを持つ型を使用する
動的なキーは別の問題です。コンパイル時にプロパティ名がわからない場合は、インデックスシグネチャが答えです:
const user: Record<string, unknown> = { name: 'Alice' };
console.log(user['name']); // OK
// より具体的な値の型を使用:
const config: Record<string, string> = {};
config.theme = 'dark'; // OK
修正方法4:型ガードで型を絞り込む
APIレスポンスはunknownとして受け取ることが多いです。型ガードを使ってアクセス前に形状を検証しましょう:
function hasName(obj: unknown): obj is { name: string } {
return typeof obj === 'object' && obj !== null && 'name' in obj;
}
const data: unknown = JSON.parse(response);
if (hasName(data)) {
console.log(data.name); // TypeScriptはこれが安全だと認識する
}
修正方法5:オプショナルチェーン(真にオプショナルなプロパティ向け)
プロパティが存在する場合としない場合があり、それが意図的なこともあります。型でオプショナルとして宣言し、呼び出し側で不在のケースを処理しましょう:
interface User {
name?: string;
}
const user: User = {};
console.log(user.name ?? 'Anonymous'); // OK
注意すべき点として、オプショナルチェーンだけではts(2339)は修正されません。プロパティはオプショナルとしてマークされていても、型定義に含まれている必要があります。
修正方法6:既存の型を拡張する
ライブラリの型はカスタムフィールドをカバーしないことがほとんどです。最初から再定義せず、拡張しましょう:
// カスタム認証フィールドを持つExpress Request
import { Request } from 'express';
interface AuthRequest extends Request {
user?: { name: string };
}
app.get('/', (req: AuthRequest, res) => {
console.log(req.user?.name); // OK
});
修正方法7:再利用可能な関数にジェネリクスを使用する
ジェネリクスは再利用性の問題を解決します。必要なプロパティを要求するよう型パラメータを制約しましょう:
function getName<T extends { name: string }>(obj: T): string {
return obj.name; // OK — Tには'name'が保証されている
}
getName({ name: 'Alice', age: 30 }); // 動作する
getName({}); // ts(2345) — 関数内部に埋もれず、呼び出し側で検出される
修正の確認
--noEmitオプションを付けてコンパイラを実行し、出力ファイルを生成せずにプロジェクト全体をチェックします:
npx tsc --noEmit
出力がクリーンであれば、ts(2339)は解消されています。VS Codeでは、保存した直後に赤い下線が消えます。変数にホバーすると、ツールチップに{}ではなくインターフェース名が表示されるはずです。
予防策
tsconfig.jsonでstrictモードを有効にする:"strict": true。ts(2339)や関連エラーを、より大きな問題に波及する前に早期に検出できます。- APIレスポンスに
anyを使わない。unknownを使い、型ガードで絞り込むことで型システムの正確性を保ちます。 - **実装前に型を書く。**インターフェースはすぐに定義でき、後のデバッグ時間を大幅に節約できます。
satisfies演算子を使用する(TypeScript 4.9以降)。型を拡大せずにオブジェクトが型に一致するか検証できます:
const user = { name: 'Alice', age: 30 } satisfies User;
console.log(user.name); // OK — 型はUserに拡大されず、狭いまま保たれる

