TL;DR
TypeScriptが関数内のthisが何を指しているか判断できない状態です。3つの解決方法があります:
- アロー関数に切り替える — 周囲のスコープから
thisを自動的に継承します。 - 関数の最初の引数として型付きの
thisパラメータを宣言する。 - メソッドに明示的な
this型を指定した上で.bind(this)を使用する。
このエラーが発生する場面
tsconfig.jsonでnoImplicitThis(またはstrictモード)が有効になっている場合に、TypeScriptが静的に解決できないthisを検出したときにエラーが発生します。主に4つの状況でよく起こります:
- DOMイベントリスナーとして使用される通常の
function forEach、setTimeoutなどに渡されるスタンドアロンのコールバック- 別の場所でコールバックとして抽出・使用されるクラスメソッド
- ネストされた関数内で
thisを参照するオブジェクトリテラルのメソッド
// tsconfig.json
{
"compilerOptions": {
"strict": true // noImplicitThis を含む各種チェックを有効にする
}
}
根本原因
JavaScriptのthisは、関数が定義された場所ではなく、どのように呼び出されるかによって決まります。これが核心的な問題です。TypeScriptは実行時の呼び出し箇所を追跡できないため、通常のfunctionを記述して内部でthisを使用すると、コンパイラはどのオブジェクトにバインドされるか分からず、推測を拒否します。
// ❌ エラー: 'this' implicitly has an 'any' type.
document.querySelector('button')?.addEventListener('click', function () {
console.log(this.textContent); // TypeScript: ここでの 'this' は何?
});
// ❌ 通常のコールバックでも同じ問題が発生する
const items = [1, 2, 3];
items.forEach(function (item) {
console.log(this); // エラー: 'this' implicitly has an 'any' type.
});
修正方法1:アロー関数を使用する(最も一般的な修正)
アロー関数には独自のthisがありません。外側のレキシカルスコープからthisを取得します — これはクラスメソッド内で求められる動作そのものです。
class FormHandler {
private label = 'Submit';
init() {
document.querySelector('button')?.addEventListener('click', () => {
// ✅ 'this' は FormHandler のインスタンスを指す
console.log(this.label);
});
}
}
thisをまったく使わないコールバックでも、アロー関数は曖昧さを解消します:
// ✅ シンプル — 'this' なし、問題なし
[1, 2, 3].forEach((item) => {
console.log(item);
});
修正方法2:明示的なthisパラメータを追加する
TypeScriptはthisという名前の仮の第一パラメータをサポートしています。関数の実際のシグネチャを変更せずに型を宣言できます — コンパイル時に完全に取り除かれます。
// ✅ TypeScript が 'this' の型を正確に把握できるようになる
function handleClick(this: HTMLButtonElement, event: MouseEvent) {
console.log(this.textContent);
}
document.querySelector('button')?.addEventListener('click', handleClick);
このパターンは、ハンドラーを複数の呼び出し箇所で再利用する場合に効果的です。TypeScriptは呼び出し元が宣言された型にバインドすることを強制します — ミスは本番環境ではなくコンパイル時に検出されます。
修正方法3:オブジェクトリテラルのメソッドで明示的なthisを使用する
少し分かりにくいパターンとして、オブジェクトリテラルのメソッドがネストされたfunctionをコールバックとして渡す場合があります。外側のメソッドは正しいコンテキストを持っていますが、ネストされた関数では完全に失われます。
// ❌ ネストされた関数で 'this' が失われる
const counter = {
count: 0,
start() {
setInterval(function () {
this.count++; // エラー: 'this' implicitly has an 'any' type.
}, 1000);
}
};
// ✅ アロー関数が start() から 'this' を継承する
const counter = {
count: 0,
start() {
setInterval(() => {
this.count++; // 'this' は counter オブジェクトを指す
}, 1000);
}
};
修正方法4:明示的なthis型と.bind()を使用する
サードパーティのコードや古いパターンを扱う場合、.bind()は機能しますが、TypeScriptは単純な.bind()呼び出しで型情報を失います。解決策:まずメソッドにthis型を宣言してからバインドします。
class Tooltip {
message = 'Hello';
show(this: Tooltip) {
console.log(this.message);
}
register() {
// ✅ 安全 — 'show' にはすでに明示的な 'this' 型が指定されている
document.addEventListener('mouseover', this.show.bind(this));
}
}
修正方法5:レガシーコードでnoImplicitThisを無効にする(最終手段)
大規模なJavaScriptコードベースをTypeScriptに移行中の場合、リリース前に200ファイルをすべて修正することはできないこともあります。一時的な回避策が存在しますが、恒久的な解決策として扱わないでください。
// tsconfig.json — 移行計画がある場合のみ使用する
{
"compilerOptions": {
"strict": true,
"noImplicitThis": false
}
}
まだ修正が必要なファイルを追跡し(TypeScriptの// @ts-checkコメントが役立ちます)、移行が完了したらフラグを再度有効にしてください。
確認方法
出力ファイルを生成せずにエラーのみを報告するチェック専用モードでコンパイラを実行します:
# 1ファイルをチェックする
npx tsc --noEmit src/your-file.ts
# プロジェクト全体をチェックする
npx tsc --noEmit
出力がなければ問題なし(終了コード0)です。まだエラーが表示される場合は、スコープを注意深く確認してください。よくある間違いとして、外側の関数は修正したものの、その中にネストされたfunctionキーワードが残っている場合があります — その内側の関数が再び曖昧さを引き起こします。
判断フローガイド
- クラスメソッドのコールバック内 → アロー関数を使用する
- スタンドアロンの再利用可能なハンドラー → 明示的な
this: SomeTypeパラメータを追加する - ネストされたコールバックを持つオブジェクトリテラル → ネストされたコールバックにアロー関数を使用する
- 移行途中のレガシーコードベース → 一時的に
noImplicitThis: falseを設定し、残りのタスクを追跡する

