Nhiều người mới bắt đầu thường viết code như thế này: const x: number = 10;. Một Senior Engineer sẽ viết: const x = 10;. Tại sao? Đó là nhờ Type Inference (Suy luận kiểu).
1. Tại sao cần Type Inference?
Nếu bạn phải khai báo type cho mọi biến, code sẽ trở nên cực kỳ rác và khó đọc (như Java phiên bản cũ). Bộ não của TS có khả năng tự động hiểu được kiểu dữ liệu dựa trên giá trị khởi tạo.
2. Các cơ chế suy luận phổ biến
A. Best Common Type
Khi bạn có một mảng với nhiều kiểu dữ liệu, TS sẽ tìm một kiểu chung nhất để bao phủ tất cả.
let x = [0, 1, null]; // TS suy luận: (number | null)[]B. Contextual Typing
Đây là khi kiểu dữ liệu được suy luận dựa trên "vị trí" mà nó xuất hiện. Đây là lý do tại sao tham số của callback trong addEventListener đã có sẵn type.
window.onmousedown = function(mouseEvent) {
console.log(mouseEvent.button); // OK: TS biết mouseEvent là MouseEvent
console.log(mouseEvent.kangaroo); // LỖI!
};3. Thử nghiệm: Sức mạnh của Inference
Hãy chạy mã dưới đây để thấy TS thông minh như thế nào khi xử lý các logic phức tạp mà không cần ta phải viết một dòng type nào.
4. Khi nào NÊN và KHÔNG NÊN khai báo tường minh?
NÊN để TS tự suy luận (Inference):
- Các biến cục bộ đơn giản.
- Giá trị khởi tạo ngay khi khai báo.
- Các hàm con đơn giản bên trong một file.
NÊN khai báo tường minh (Explicit):
- Function Arguments: Để tạo ra "hợp đồng" (contract) cho hàm.
- Function Return Types: Rất quan trọng cho các thư viện công cộng hoặc hàm phức tạp để tránh lỗi logic vô tình thay đổi giá trị trả về.
- Public APIs: Khi bạn xuất (export) các interface cho team khác dùng.
5. Senior Tip: const vs let Inference
Lưu ý rằng TS suy luận khác nhau tùy thuộc vào cách bạn khai báo biến:
const x = "loading"; // Type: "loading" (Literal Type - Chính xác tuyệt đối)
let y = "loading"; // Type: string (Widened Type - Có thể thay đổi sau này)Sử dụng const bất cứ khi nào có thể giúp TS giữ cho các kiểu dữ liệu ở mức chính xác cao nhất (Literal types).
Tình huống thực tế & Phỏng vấn
Scenario: Refactoring một hàm cũ
Bối cảnh: Bạn đang bảo trì một project lớn và muốn sửa một hàm tính toán giá tiền. Hàm này được gọi ở 50 file khác nhau.
Vấn đề: Nếu hàm không được khai báo Return Type tường minh, khi bạn vô tình thay đổi giá trị trả về (ví dụ từ number sang một object { price: number }), TS chỉ báo lỗi ở 50 file kia, khiến việc tìm nguyên nhân gốc rễ trở nên khó khăn.
Giải pháp Senior: Luôn khai báo Return Type cho các hàm logic quan trọng. Khi đó, nếu bạn sửa sai, TS sẽ báo lỗi ngay tại thân hàm thay vì báo lỗi ở nơi sử dụng.
Interview Question: Senior Level
Q: "Type Widening" là gì trong cơ chế Inference của TS? Làm thế nào để ngăn chặn nó khi làm việc với các hằng số (constants)?
Gợi ý đáp án
Đáp án: Widening là khi TS suy luận một giá trị cụ thể thành một kiểu rộng hơn (ví dụ: "GET" trở thành string). Để ngăn chặn, chúng ta dùng as const (const assertion).
const config = {
method: "GET"
} as const;
// method bây giờ có type là "GET", không phải string.Tổng kết
Hiểu về bộ não suy luận của TypeScript giúp bạn viết code sạch hơn (less noise) nhưng vẫn giữ được sự an toàn tối đa. Quy tắc ngón tay cái của Senior: "Nếu TypeScript có thể tự hiểu, hãy để nó tự làm. Chỉ can thiệp khi bạn muốn giới hạn hoặc làm rõ ý định của mình."