Chuyện Gì Đã Xảy Ra
Bạn đang làm việc với một dự án TypeScript và trình biên dịch — hoặc gạch đỏ của VS Code — ném cho bạn lỗi này:
Type 'string' is not assignable to type 'number'. ts(2322)
Mã lỗi ts(2322) có nghĩa là kiểu dữ liệu không khớp: bạn đang truyền một string cho thứ gì đó cần một number. Đây có lẽ là lỗi kiểu phổ biến nhất bạn sẽ gặp trong TypeScript — và hầu như lúc nào cũng có thể sửa trong vòng năm phút nếu bạn biết cần tìm gì.
Các Tình Huống Thường Gặp
Bốn tình huống dưới đây chiếm phần lớn các lỗi loại này. Nhận ra chúng và bạn sẽ hiếm khi bị bí.
1. Gán một chuỗi ký tự vào biến kiểu number
let age: number = "25"; // Error: Type 'string' is not assignable to type 'number'
Trường hợp kinh điển. Biến cần kiểu number, nhưng bạn lại truyền vào một string.
2. Kiểu trả về của hàm không khớp
function getScore(): number {
return "100"; // Error: Type 'string' is not assignable to type 'number'
}
Cùng vấn đề, khác vị trí — hàm hứa hẹn trả về number nhưng lại trả về string.
3. Kiểu thuộc tính của object không khớp
interface User {
id: number;
name: string;
}
const user: User = {
id: "abc123", // Error: Type 'string' is not assignable to type 'number'
name: "Alice",
};
4. Dữ liệu từ API hoặc form input (cực kỳ phổ biến)
// Giá trị input của form luôn là string — không có ngoại lệ
const input = document.getElementById("age") as HTMLInputElement;
const age: number = input.value; // Error: 'string' not assignable to 'number'
Lỗi này khiến nhiều lập trình viên điêu đứng. DOM trả về string. TypeScript yêu cầu bạn phải chuyển đổi tường minh.
Cách Debug
Bắt đầu bằng cách tìm đúng dòng lỗi. Trong VS Code, di chuột lên gạch đỏ — TypeScript sẽ cho bạn biết chính xác biến nào đang xung đột và tại sao. Thích dùng terminal hơn? Chạy lệnh:
npx tsc --noEmit
Lệnh này kiểm tra kiểu toàn bộ dự án mà không tạo ra file output. Mọi lỗi ts(2322) sẽ hiển thị kèm đường dẫn file và số dòng.
Sau khi xác định được vị trí lỗi, ba câu hỏi sau giúp bạn thu hẹp hướng sửa:
- Biến này thực sự nên là
stringhaynumber? - Dữ liệu có đến từ nguồn bên ngoài không — API, trường form, hay biến môi trường?
- Có ai đó vừa thay đổi interface hoặc chữ ký hàm gần đây không?
Cách Sửa
Cách 1: Chuyển đổi string sang number
Khi giá trị là string nhưng bạn thực sự cần number, hãy chuyển đổi tường minh. JavaScript cung cấp cho bạn ba công cụ:
// Number() — dùng được cho cả số nguyên lẫn số thập phân
const age: number = Number(input.value);
// parseInt — bỏ phần thập phân, luôn truyền hệ cơ số 10
const count: number = parseInt(input.value, 10);
// parseFloat — giữ lại phần thập phân
const price: number = parseFloat(input.value);
Lưu ý: Number("abc") không throw lỗi — nó âm thầm trả về NaN. Về mặt kỹ thuật đây vẫn là number trong JavaScript, nhưng nó sẽ phá vỡ mọi phép tính bạn thực hiện với nó. Hãy luôn kiểm tra:
const age = Number(input.value);
if (isNaN(age)) {
throw new Error("Invalid age value");
}
Cách 2: Thay đổi kiểu annotation nếu string mới là đúng
Đôi khi annotation mới là chỗ có bug, không phải giá trị. Nếu biến thực sự nên chứa string, hãy sửa lại kiểu:
// Đổi kiểu dữ liệu
let id: string = "abc123";
// Hoặc dùng union type nếu nó có thể hợp lệ ở cả hai dạng
let id: string | number = "abc123";
Cách 3: Sửa kiểu trả về của hàm
// Lựa chọn A: trả về đúng kiểu
function getScore(): number {
return 100; // number thực sự, không phải string
}
// Lựa chọn B: cập nhật annotation kiểu trả về
function getScore(): string {
return "100";
}
Cách 4: Cập nhật interface cho khớp với thực tế
Dữ liệu trả về từ API thường có cấu trúc khác với những gì bạn giả định khi viết interface. Hãy sửa interface thay vì ép dữ liệu phải khớp:
interface User {
id: string; // đổi từ number — API thực tế trả về string
name: string;
}
const user: User = {
id: "abc123", // hợp lệ rồi
name: "Alice",
};
Cách 5: Xử lý trường hợp biến môi trường
Trong Node.js, mọi biến môi trường đều có kiểu string | undefined — không có ngoại lệ. Vì vậy process.env.PORT không bao giờ là number, dù bạn đặt 3000 trong file .env:
// Lỗi này:
const port: number = process.env.PORT; // Kiểu: string | undefined
// Sửa — chuyển đổi và cung cấp giá trị mặc định:
const port: number = Number(process.env.PORT) || 3000;
Khi Dữ Liệu Đến Từ API
Type assertion kiểu as MyInterface là một cái bẫy ở đây. Nó nói với TypeScript hãy tin tưởng bạn, nhưng không hề kiểm tra gì ở runtime. Nếu API thay đổi cấu trúc response, code của bạn sẽ âm thầm hỏng:
// Rủi ro — không có validation lúc runtime
const data = response.json() as MyInterface;
// An toàn hơn — zod kiểm tra cấu trúc lúc runtime và throw lỗi sớm nếu sai
import { z } from "zod";
const UserSchema = z.object({
id: z.number(),
name: z.string(),
});
const user = UserSchema.parse(await response.json());
Nếu API gửi id: "abc123" trong khi bạn mong đợi là number, zod sẽ throw lỗi ngay lập tức với thông báo rõ ràng — thay vì để NaN lan truyền khắp ứng dụng.
Xác Nhận Đã Sửa Xong
Chạy trình biên dịch. Không có output nghĩa là không có lỗi:
npx tsc --noEmit
Gạch đỏ trong VS Code sẽ biến mất ngay khi bạn lưu file. Nếu vẫn còn, hãy restart TS language server: mở command palette (Ctrl+Shift+P / Cmd+Shift+P) và chạy TypeScript: Restart TS Server.
Bài Học Rút Ra
- Bỏ qua
as anyvà// @ts-ignore. Chúng tắt tiếng lỗi nhưng đảm bảo gây ra bug lúc runtime về sau. - Input của form, tham số URL, và biến môi trường luôn là string. Hãy chuyển đổi chúng trước khi đưa vào bất kỳ nơi nào có kiểu dữ liệu.
- Khi interface và dữ liệu thực tế không khớp, hãy sửa interface — không phải dữ liệu.
- ts(2322) là TypeScript đang làm đúng nhiệm vụ của nó. Nó đang bắt một bug mà chỉ phát nổ lúc runtime, có thể ngay trên môi trường production.

