Lỗi
Argument of type 'string' is not assignable to parameter of type 'number'. ts(2345)
Bạn đã truyền một giá trị vào hàm, nhưng TypeScript từ chối vì kiểu dữ liệu không khớp. Chín trường hợp trong mười, bạn đang có một string ở chỗ hàm cần number — nhưng sự không khớp tương tự cũng xuất hiện với boolean vs string, null vs object, và nhiều tổ hợp khác.
Tin tốt: đây là lỗi biên dịch. TypeScript đã phát hiện bug trước khi nó lên production.
Nguyên Nhân
TypeScript kiểm tra mọi lời gọi hàm theo kiểu tham số đã khai báo. Truyền một string cho tham số kiểu number — dù là "42" — trình biên dịch cũng từ chối. Nó không quan tâm giá trị trông có vẻ là số.
Các nguyên nhân thường gặp:
- Đọc từ
process.argv,req.query, hoặcURLSearchParams— những thứ này luôn trả về string, kể cả khi giá trị là"42". - Lấy giá trị từ thẻ
<input>trong HTML —input.valueluôn làstring, không có ngoại lệ. - Parse JSON khi một trường trả về
"10"thay vì10. - Một biến ban đầu là
numbernhưng bị mở rộng thànhstring | numberở đâu đó phía trên. - Truyền đối số vào hàm thư viện theo thứ tự sai.
Cách Sửa Từng Bước
Bước 1 — Tìm chỗ không khớp
TypeScript chỉ cho bạn đúng dòng bị lỗi. Mở file đó, xem bạn đang truyền vào gì, rồi kiểm tra chữ ký hàm. Vấn đề thường rất rõ ràng:
// ts(2345)
function double(n: number): number {
return n * 2;
}
const input = "5"; // kiểu: string
console.log(double(input)); // ❌ string → number không khớp
Bước 2 — Chuyển đổi giá trị
Một string biểu diễn số cần được parse trước khi truyền vào.
const input = "5";
console.log(double(Number(input))); // ✅ tường minh, xử lý được số thập phân
console.log(double(parseInt(input, 10))); // ✅ tốt hơn cho số nguyên — luôn truyền radix 10
console.log(double(+input)); // ✅ ngắn gọn, nhưng dễ bị bỏ sót khi review code
Dùng Number() khi giá trị có thể là số thập phân. Dùng parseInt(input, 10) khi bạn cần cụ thể là số nguyên. Tránh dùng toán tử một ngôi + trong codebase nhóm — khó grep và trông như lỗi đánh máy.
Bước 3 — Xử lý các nguồn phổ biến
Tham số dòng lệnh qua process.argv:
const raw = process.argv[2]; // string | undefined
const port = Number(raw);
if (isNaN(port)) throw new Error(`Invalid port: ${raw}`);
startServer(port); // ✅
Trường input HTML:
const input = document.getElementById("age") as HTMLInputElement;
const age = Number(input.value);
if (isNaN(age)) return; // bắt cả chuỗi rỗng và "abc"
setAge(age); // ✅
Tham số URL query (Express / Fetch API):
// Express
app.get("/users", (req, res) => {
const page = Number(req.query.page); // req.query.page là string | string[] | ParsedQs
getUsers(page); // ✅
});
// URLSearchParams
const params = new URLSearchParams(location.search);
const id = Number(params.get("id")); // get() trả về string | null
if (isNaN(id)) throw new Error("Missing id param");
fetchItem(id); // ✅
JSON payload với số dạng string:
// Server trả về { "count": "10" } thay vì { "count": 10 }
const data = JSON.parse(response) as { count: string };
const count = Number(data.count);
renderList(count); // ✅
Bước 4 — Thu hẹp kiểu union
TypeScript đôi khi mở rộng biến thành string | number. Một hàm chỉ chấp nhận number vẫn sẽ từ chối. Dùng type guard để thu hẹp trước khi truyền.
function applyDiscount(price: number): number {
return price * 0.9;
}
const rawPrice: string | number = getPrice();
// Cách A: thu hẹp bằng typeof
if (typeof rawPrice === "number") {
applyDiscount(rawPrice); // ✅ TypeScript biết đây là number
}
// Cách B: chuyển đổi không điều kiện
applyDiscount(Number(rawPrice)); // ✅ hoạt động trong mọi trường hợp
Bước 5 — Sửa chữ ký hàm thay vì chỗ gọi
Đôi khi chỗ gọi hàm không có vấn đề mà chữ ký hàm mới là chỗ sai. Nếu một hàm thực sự được thiết kế để xử lý cả string lẫn number, hãy cập nhật kiểu tham số để phản ánh điều đó.
// Trước — quá chặt
function log(value: number): void {
console.log(value);
}
// Sau — chấp nhận cả hai
function log(value: number | string): void {
console.log(value);
}
Chỉ mở rộng kiểu nếu hàm thực sự xử lý được cả hai. Đừng làm vậy chỉ để tắt lỗi — như vậy là đánh mất ý nghĩa của TypeScript.
Xác Nhận Đã Sửa Xong
Chạy trình biên dịch để xác nhận không còn lỗi nào:
# Kiểm tra toàn bộ project
npx tsc --noEmit
# Một file cụ thể
npx tsc --noEmit src/yourfile.ts
# Với ts-node
npx ts-node src/yourfile.ts
Không có output nghĩa là không còn lỗi kiểu nào. Trong VS Code, gạch chân đỏ biến mất ngay khi bạn lưu file.
Tham Khảo Nhanh
Number(x)— chuyển đổi string/boolean/null sang number; trả vềNaNnếu không parse đượcparseInt(x, 10)— lấy số nguyên từ string; tự động bỏ các ký tự không phải số ở cuối ("42px"→42)parseFloat(x)— lấy số thập phân từ string; hữu ích với"3.14"+x— viết tắt củaNumber(x); tránh dùng trong codebase nhóm vì khó grep- Luôn kiểm tra với
isNaN()khi dữ liệu đến từ người dùng hoặc nguồn bên ngoài
Khi Nào Chuyển Đổi Không Phải Là Cách Đúng
Thấy ts(2345) xuất hiện ở hàng chục chỗ? Trước khi rải Number() khắp codebase, hãy kiểm tra lại data model của bạn. UUID và MongoDB ObjectID về bản chất là string — chuyển đổi chúng sang number ở mỗi chỗ gọi là đang chống lại bản chất vấn đề. Hãy khai báo kiểu chúng là string ngay từ đầu và các lỗi sẽ biến mất hẳn. Lỗi ts(2345) xuất hiện tràn lan thường là triệu chứng của sự không khớp kiểu ở tầng dữ liệu, chứ không phải vấn đề chuyển đổi ở từng điểm riêng lẻ.

