Bài 2 · Cơ bản · 20 phút
Kiểu cơ bản & suy luận
Biên soạn bởi Nguyễn Anh Tuấn
Kiểu nguyên thuỷ, mảng, tuple, object và literal type; phân biệt any vs unknown vs never (vì sao tránh any, dùng unknown an toàn). Suy luận kiểu (type inference) để TS tự đoán thay vì chú thích mọi nơi, và as const để khoá literal.
Nhiều người tưởng dùng TypeScript là phải ghi kiểu cho mọi thứ. Thật ra
ngược lại: TypeScript rất giỏi tự suy ra kiểu (type inference) từ giá trị
bạn gán. Viết let tuoi = 5 là TS đã biết tuoi kiểu number rồi - thêm : number chỉ
tổ thừa.
Đoán thử xem TypeScript suy ra kiểu gì cho mỗi khai báo, rồi bấm để kiểm:
Đoán xem TypeScript suy ra kiểu gì, rồi bấm để kiểm:
let tuoi = 5; const ten = "Bo"; let ten = "Bo"; const diem = [1, 2, 3]; const meo = { ten: "Bo", tuoi: 2 }; const mau = ["do", "xanh"] as const; Để ý hai dòng const ten = "Bo" và let ten = "Bo":
cùng giá trị mà TS suy ra khác kiểu. Đó gọi là widening (nới kiểu), ta sẽ mổ kỹ ở Bước 3.
- ▸TypeScript tự suy ra kiểu từ giá trị khởi tạo - không cần chú thích ở mọi nơi.
- ▸Chú thích kiểu chỉ cần ở chỗ TS không đoán được (tham số hàm, container rỗng).
- ▸Để TS suy luận giúp code gọn hơn mà vẫn an toàn y như chú thích tay.
Bộ kiểu cơ bản của TypeScript phủ lên đúng các kiểu giá trị JavaScript bạn đã quen:
cac-kieu-nen.ts
// Nguyen thuy
let ten: string = "Bo";
let tuoi: number = 2;
let deThuong: boolean = true;
// Mang: danh sach dai tuy y, cung kieu
let diem: number[] = [9, 8, 10];
// Tuple: do dai CO DINH, moi vi tri mot kieu
let cap: [string, number] = ["Bo", 2];
// Object: mo ta hinh dang
let meo: { ten: string; tuoi: number } = { ten: "Bo", tuoi: 2 }; Tuple không phải array
number[] là danh sách dài bao nhiêu cũng được, mọi phần tử cùng kiểu. [string, number] là tuple: đúng 2 phần tử, vị trí đầu là chuỗi, vị trí sau là số.
Dùng tuple khi thứ tự có ý nghĩa, vd trả về một cặp [tên, tuổi].- ▸Nguyên thuỷ: string, number, boolean (và null, undefined).
- ▸Mảng number[] dài tuỳ ý cùng kiểu; tuple [string, number] cố định vị trí và kiểu.
- ▸Object type mô tả hình dạng: tên thuộc tính kèm kiểu của nó.
Một literal type là khi kiểu hẹp lại đúng bằng một giá trị cụ thể:
không phải "một chuỗi bất kỳ" mà đúng chữ "do". Ghép nhiều literal
bằng dấu | cho ta một tập giá trị hợp lệ:
literal.ts
let mau: "do" | "xanh" | "vang";
mau = "xanh"; // OK
mau = "tim"; // Loi: "tim" khong nam trong tap cho phep Nhớ vụ widening ở Bước 1: let nới literal
thành kiểu rộng (string), còn const giữ literal. Khi muốn khoá cả
một object hay mảng lại thành literal, dùng as const:
as-const.ts
const mau1 = ["do", "xanh"]; // suy ra: string[]
const mau2 = ["do", "xanh"] as const; // suy ra: readonly ["do", "xanh"] - ▸Literal type: kiểu hẹp đúng bằng một giá trị; ghép bằng | thành tập giá trị hợp lệ.
- ▸let nới literal thành kiểu rộng; const giữ nguyên literal.
- ▸as const khoá object/mảng thành literal readonly - nền cho discriminated union sau này.
any là cửa thoát: nó tắt mọi kiểm tra kiểu. Gán gì cũng được, gọi
gì cũng được, TS im lặng cho qua - đúng nghĩa vứt đi tấm lưới an toàn mà Bài 1 đã dựng.
Lỗi lại trôi tới lúc chạy:
any biên dịch OK, nhưng chạy thì nổ
let a: any = "hello";
a.toFixed(2); // tsc khong bao gi; "hello" lam gi co .toFixed Kết quả khi chạy
TypeError: a.toFixed is not a function
unknown là phiên bản an toàn của any: cũng
nhận mọi giá trị, nhưng TS bắt bạn kiểm tra trước khi dùng. Cùng đoạn trên với unknown, tsc chặn ngay:
unknown: tsc bắt kiểm trước khi dùng
let u: unknown = "hello";
u.toFixed(2); tsc báo
error TS18046: 'u' is of type 'unknown'. Muốn dùng u, phải thu hẹp kiểu (narrowing) cho
TS biết chắc nó là gì - một kỹ thuật ta sẽ học sâu ở bài Union & narrowing:
unknown an toàn sau khi kiểm
let u: unknown = 3.14159;
if (typeof u === "number") {
console.log(u.toFixed(2)); // OK - trong if, TS biet u la number
} Kết quả khi chạy
3.14
Cuối cùng, never là kiểu của thứ không bao giờ
có giá trị: một hàm luôn ném lỗi (không bao giờ trả về), hay một nhánh "lẽ ra không xảy ra".
never.ts
function boom(): never {
throw new Error("dung lai");
} Trung thực
Bạn vừa có bộ kiểu nền và hiểu rằng TypeScript tự đoán rất nhiều, chỉ cần chú thích ở chỗ nó chịu thua. Nhưng các object type ở Bước 2 mới chỉ là khởi đầu: khi hình dạng dữ liệu lớn dần, ta cần cách đặt tên và tái dùng chúng.
Bài tiếp theo: Interface & structural typing
Câu hỏi thường gặp
Không. Hãy để TypeScript tự suy luận với những biến gán giá trị ngay - chú thích lúc đó chỉ là thừa và rườm rà. Bạn chỉ cần chú thích ở chỗ TS không đoán được hoặc đoán không đúng ý: tham số hàm, mảng/object khởi tạo rỗng, hoặc khi muốn ép một kiểu rộng/hẹp hơn.
Cả hai đều nhận mọi giá trị, nhưng any TẮT kiểm tra (bạn làm gì với nó TS cũng im lặng, nên lỗi trôi tới lúc chạy), còn unknown thì AN TOÀN: TS bắt bạn kiểm tra (narrowing) để biết chắc kiểu trước khi dùng. Khi cần một "ô chứa kiểu chưa biết", hãy dùng unknown thay cho any.
as const khoá một giá trị lại thành kiểu hẹp nhất: chuỗi thành literal đúng chữ đó, mảng thành tuple readonly. Rất hợp cho hằng số, cấu hình, và sau này là discriminated union - nơi bạn cần TS nhớ chính xác từng giá trị thay vì nới rộng thành string/number.
Tick những điều em tự tin làm được. Càng lên cao, em càng hiểu sâu.
Trả lời vài câu để chắc rằng em đã nắm bài.
Vì sao nên tránh any và ưu tiên unknown?
- 1
Để TS đoán hộ
Viết 5 khai báo biến với giá trị ban đầu (số, chuỗi, mảng, object, boolean). KHÔNG chú thích kiểu. Dùng widget ở Bước 1 hoặc Playground để xem TS suy ra kiểu gì.
✅ Hoàn thành khi: Một bảng 5 dòng: khai báo của mèo con kèm kiểu TypeScript suy ra cho từng dòng.
- 2
let và const khác kiểu
Viết const ten = "Bo"; và let ten2 = "Bo";. Soi kiểu TS suy ra cho mỗi cái và giải thích vì sao khác nhau.
✅ Hoàn thành khi: Ghi đúng: const ra "Bo" (literal), let ra string, kèm một câu giải thích về widening.
- 3
any nổ lúc chạy
Gán một chuỗi vào biến kiểu any rồi gọi một method của số (vd .toFixed()). Biên dịch (không lỗi) rồi chạy bằng node để thấy nó nổ.
✅ Hoàn thành khi: Một file chạy ra TypeError lúc chạy, kèm một câu: vì sao any để lọt lỗi này còn unknown thì không.
- 4
unknown buộc kiểm
Đổi biến any ở bài trên thành unknown. Xem tsc báo lỗi gì, rồi thêm một kiểm tra typeof để code biên dịch lại được.
✅ Hoàn thành khi: Đoạn unknown ban đầu bị tsc chặn, và bản có thêm typeof thì biên dịch sạch.
- 5
Khoá bằng as const
Viết một mảng các chuỗi cố định (vd các màu), thêm as const. So kiểu suy ra trước và sau khi thêm as const.
✅ Hoàn thành khi: Ghi rõ: trước as const là string[], sau as const là readonly tuple các literal đúng chữ.
- 6
Chọn chỗ chú thích
Tìm trong code của mèo con 2 chỗ chú thích kiểu là THỪA (TS đã đoán được) và 1 chỗ chú thích là CẦN (TS không đoán được, vd tham số hàm).
✅ Hoàn thành khi: Hai ví dụ chú thích thừa nên bỏ, và một ví dụ chú thích cần giữ, kèm lý do mỗi chỗ.