TL;DR
TypeScript không thể xác định this đang tham chiếu đến cái gì bên trong hàm của bạn. Ba cách giải quyết nhanh:
- Chuyển sang arrow function — tự động kế thừa
thistừ scope bao quanh. - Khai báo tham số
thiscó kiểu tường minh làm đối số đầu tiên của hàm. - Dùng
.bind(this)sau khi đã đặt kiểuthisrõ ràng cho method.
Lỗi này xuất hiện khi nào?
Lỗi xảy ra khi noImplicitThis (hoặc chế độ strict) được bật trong tsconfig.json và TypeScript phát hiện một this mà nó không thể phân giải tĩnh. Bốn tình huống thường xuyên kích hoạt lỗi này:
- Dùng
functionthông thường làm event listener cho DOM - Một callback độc lập truyền vào
forEach,setTimeout, v.v. - Một class method được tách ra và dùng làm callback ở nơi khác
- Các method trong object literal tham chiếu
thisbên trong hàm lồng nhau
// tsconfig.json
{
"compilerOptions": {
"strict": true // bật noImplicitThis cùng với các kiểm tra khác
}
}
Nguyên nhân gốc rễ
Giá trị this trong JavaScript được xác định bởi cách hàm được gọi, không phải nơi nó được định nghĩa. Đó chính là vấn đề cốt lõi. TypeScript không thể truy vết các call site lúc runtime, nên khi bạn viết một function thông thường và dùng this bên trong, trình biên dịch không biết nó sẽ được bind vào object nào. Nó từ chối đoán mò.
// ❌ Lỗi: 'this' implicitly has an 'any' type.
document.querySelector('button')?.addEventListener('click', function () {
console.log(this.textContent); // TypeScript: 'this' ở đây là gì?
});
// ❌ Vấn đề tương tự trong callback thông thường
const items = [1, 2, 3];
items.forEach(function (item) {
console.log(this); // Lỗi: 'this' implicitly has an 'any' type.
});
Cách sửa 1: Dùng arrow function (phổ biến nhất)
Arrow function không có this riêng. Chúng lấy this từ scope lexical bao quanh — đây chính xác là điều bạn muốn khi dùng bên trong một class method.
class FormHandler {
private label = 'Submit';
init() {
document.querySelector('button')?.addEventListener('click', () => {
// ✅ 'this' tham chiếu đến instance của FormHandler
console.log(this.label);
});
}
}
Các callback không dùng this? Arrow function cũng giúp loại bỏ sự mơ hồ trong những trường hợp đó:
// ✅ Gọn gàng — không có 'this', không có vấn đề
[1, 2, 3].forEach((item) => {
console.log(item);
});
Cách sửa 2: Thêm tham số this tường minh
TypeScript hỗ trợ một tham số giả có tên this ở vị trí đầu tiên. Nó cho phép bạn khai báo kiểu mà không ảnh hưởng đến signature thực của hàm — trình biên dịch sẽ loại bỏ hoàn toàn tham số này trong quá trình biên dịch.
// ✅ TypeScript giờ biết chính xác 'this' là gì
function handleClick(this: HTMLButtonElement, event: MouseEvent) {
console.log(this.textContent);
}
document.querySelector('button')?.addEventListener('click', handleClick);
Pattern này đặc biệt hữu ích khi một handler được tái sử dụng ở nhiều call site. TypeScript bắt buộc các caller phải bind đúng kiểu đã khai báo — lỗi sẽ bị phát hiện lúc biên dịch, không phải trên production.
Cách sửa 3: Khai báo this tường minh trong object literal method
Đây là một biến thể khó nhận ra: một method trong object literal truyền một function lồng nhau làm callback. Method bên ngoài có context đúng; hàm lồng nhau bên trong lại mất hoàn toàn context đó.
// ❌ Hàm lồng nhau mất 'this'
const counter = {
count: 0,
start() {
setInterval(function () {
this.count++; // Lỗi: 'this' implicitly has an 'any' type.
}, 1000);
}
};
// ✅ Arrow function kế thừa 'this' từ start()
const counter = {
count: 0,
start() {
setInterval(() => {
this.count++; // 'this' là object counter
}, 1000);
}
};
Cách sửa 4: Dùng .bind() với kiểu this tường minh
Làm việc với code bên thứ ba hoặc các pattern cũ? .bind() vẫn hoạt động, nhưng TypeScript sẽ mất thông tin kiểu khi dùng .bind() đơn thuần. Giải pháp: khai báo kiểu this trên method trước, rồi mới bind.
class Tooltip {
message = 'Hello';
show(this: Tooltip) {
console.log(this.message);
}
register() {
// ✅ An toàn — 'show' đã mang kiểu 'this' tường minh
document.addEventListener('mouseover', this.show.bind(this));
}
}
Cách sửa 5: Tắt noImplicitThis cho code legacy (phương án cuối cùng)
Đang migrate một codebase JavaScript lớn sang TypeScript? Đôi khi bạn không thể sửa hết 200 file trước khi release. Có một lối thoát tạm thời — nhưng đừng coi đó là giải pháp lâu dài.
// tsconfig.json — chỉ dùng khi bạn có kế hoạch migration rõ ràng
{
"compilerOptions": {
"strict": true,
"noImplicitThis": false
}
}
Theo dõi các file vẫn cần sửa (comment // @ts-check của TypeScript rất hữu ích), và bật lại flag này khi quá trình migration hoàn tất.
Kiểm tra
Chạy trình biên dịch ở chế độ check-only — không tạo file output, chỉ báo lỗi:
# Kiểm tra một file
npx tsc --noEmit src/your-file.ts
# Kiểm tra toàn bộ project
npx tsc --noEmit
Không có output nghĩa là sạch lỗi (exit code 0). Vẫn còn thấy lỗi? Kiểm tra kỹ scope. Một lỗi rất phổ biến: bạn đã sửa hàm bên ngoài nhưng vẫn còn từ khóa function lồng bên trong — hàm bên trong đó lại tái sinh ra sự mơ hồ.
Hướng dẫn quyết định nhanh
- Callback bên trong class method → dùng arrow function
- Handler độc lập có thể tái sử dụng → thêm tham số
this: SomeTypetường minh - Object literal có callback lồng nhau → dùng arrow function cho callback lồng nhau
- Codebase legacy đang trong quá trình migration → đặt
noImplicitThis: falsetạm thời, theo dõi phần còn lại cần sửa

