Menu

Literal type và as const trong TypeScript, kèm ví dụ

Literal type là kiểu chỉ có đúng một giá trị, như "GET" hay 404. Tìm hiểu literal type cho chuỗi, số và boolean, union của các literal, vì sao let bị mở rộng kiểu còn const thì không, as const làm gì và const type parameter.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Literal type là kiểu chỉ có đúng một giá trị: "up" là kiểu có thành viên duy nhất là chuỗi "up", và 404 là kiểu có thành viên duy nhất là số 404. Đứng riêng thì chúng không hữu ích lắm. Khi ghép thành union, chúng cho bạn một biến chỉ nhận một tập giá trị cố định và không gì khác.

Nếu không có chú thích @ts-expect-error, lời gọi cuối cùng là lỗi biên dịch (TS2345). Có chú thích đó, chương trình biên dịch được, lời gọi vẫn chạy và in ra moving north by 1: literal type chỉ tồn tại với trình biên dịch, còn lúc chạy giá trị là một chuỗi bình thường.

Literal chuỗi, số và boolean

Mọi giá trị chuỗi, số, bigint hay boolean đều có thể viết thành kiểu. Khi đó trình biên dịch chỉ chấp nhận đúng giá trị ấy.

let method: "GET" = "GET";
let code: 404 = 404;
let ok: true = true;
let big: 10n = 10n;

type Port = 80 | 443 | 8080;
const port: Port = 443;

Bản thân boolean chỉ là union true | false, vì vậy khi thu hẹp một boolean bằng if (flag), nhánh else còn lại false.

Literal typeCho phépKiểu rộng hơn
"GET"chỉ chuỗi "GET"string
404chỉ số 404number
10nchỉ bigint 10bigint
truechỉ trueboolean

Union của các literal

Cách dùng phổ biến là một union liệt kê mọi giá trị được phép. Bên trong hàm, trình biên dịch thu hẹp union khi bạn kiểm tra, nên mỗi nhánh biết chính xác nó đang giữ giá trị nào.

Union của các string literal là lựa chọn thay thế enum quen thuộc trong TypeScript. Nó không tốn gì lúc chạy, các giá trị là chuỗi thường mà bạn có thể log ra hay gửi qua JSON, và gõ sai là lỗi biên dịch. Xem thêm về ưu nhược điểm ở trang enum.

Mở rộng kiểu: let và const

Khi TypeScript suy ra kiểu từ một literal, nó xem giá trị có thể thay đổi hay không. Biến const không bao giờ gán lại được, nên giữ literal type. Biến let thì gán lại được, nên kiểu của nó được mở rộng thành kiểu tổng quát.

Rê chuột lên từng tên trong trình soạn thảo để xem kiểu được suy ra. Nếu bạn muốn một biến let chỉ giữ một số giá trị nhất định, hãy chú thích kiểu: let mode: "light" | "dark" = "light".

Vì sao thuộc tính của đối tượng bị mở rộng kiểu

Thuộc tính của một object literal có thể thay đổi, nên chúng cũng bị mở rộng kiểu, kể cả khi đối tượng được lưu trong một const. Đây là cách phổ biến nhất để bạn vô tình gặp literal type:

Trình biên dịch báo:

index.ts(7,15): error TS2345: Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.

req được suy ra là { url: string; method: string } vì code phía sau có thể chạy req.method = "DELETE". Có ba cách sửa:

Cách thứ tư là satisfies, vừa kiểm tra đối tượng theo một kiểu vừa giữ literal type cho các thuộc tính.

as const

as const là một const assertion. Đặt nó sau một biểu thức, trình biên dịch sẽ suy ra kiểu hẹp nhất có thể:

  • giá trị chuỗi, số và boolean giữ literal type
  • thuộc tính của đối tượng thành readonly
  • mảng literal thành tuple readonly có độ dài cố định

Assertion này chỉ có tác dụng lúc biên dịch. JavaScript được sinh ra là chính object literal đó, bỏ đi as const, nên không gì ngăn code khác thay đổi nó lúc chạy. Nếu bạn cần đảm bảo lúc chạy, hãy gọi thêm Object.freeze.

Union type từ một mảng as const

Một mẫu hay gặp là giữ các giá trị được phép trong một mảng, có thể lặp qua lúc chạy, rồi suy ra union type từ mảng đó. (typeof arr)[number] nghĩa là "kiểu của một phần tử bất kỳ trong arr".

Không có as const, ROLES sẽ là string[] và Role chỉ là string. Phép ép kiểu sang readonly string[] trong isRole là cần thiết vì includes trên tuple các literal chỉ nhận đúng những literal đó, trong khi mục đích của hàm là kiểm tra một chuỗi có thể không thuộc số đó. Mẫu đối tượng tương tự cũng dùng được cho bảng khóa/giá trị: const Status = { Active: "active", Banned: "banned" } as const, rồi type Status = (typeof Status)[keyof typeof Status].

const type parameter

Một hàm generic thường mở rộng kiểu của các literal bạn truyền vào. Từ TypeScript 5.0, bạn có thể đánh dấu một type parameter là const, khiến trình biên dịch suy ra đối số như thể nó có as const, mà không bắt người gọi phải tự viết.

Đây chủ yếu là công cụ cho tác giả thư viện: định nghĩa route, builder và các hàm hỗ trợ schema dùng nó để người gọi có kiểu chính xác từ literal thông thường.

Các ý nghĩa của const

Từ khóa const xuất hiện ở bốn chỗ khác nhau trong code TypeScript:

Cú phápLoạiTác dụng
const x = 1khai báo JavaScriptbiến không thể gán lại; giá trị literal giữ literal type
expr as constassertion TypeScriptkiểu hẹp nhất: literal, thuộc tính readonly, tuple readonly
function f<const T>()type parameter TypeScriptsuy ra đối số như thể chúng có as const
const enum E {}enum TypeScriptenum có các thành viên được chèn thẳng vào code lúc biên dịch

Không cái nào đóng băng đối tượng lúc chạy. const obj = { a: 1 } vẫn cho phép obj.a = 2; chỉ việc gán lại chính obj mới là lỗi.

Câu hỏi thường gặp

Literal type trong TypeScript là gì?

Là kiểu chỉ cho phép đúng một giá trị. "GET" là kiểu có giá trị duy nhất là chuỗi "GET", 404 là kiểu có giá trị duy nhất là số 404, và true là kiểu có giá trị duy nhất là true. Chúng hữu ích nhất khi ghép thành union, như type Method = "GET" | "POST".

as const làm gì trong TypeScript?

as const là một const assertion. Nó bảo trình biên dịch suy ra kiểu hẹp nhất cho một biểu thức: giá trị chuỗi và số giữ literal type, thuộc tính của đối tượng thành readonly, còn mảng literal thành tuple readonly. Nó chỉ thay đổi kiểu; lúc chạy, giá trị vẫn là đối tượng hay mảng bình thường và không bị đóng băng.

Vì sao TypeScript suy ra string thay vì literal của tôi?

Vì giá trị đó có thể thay đổi. let x = "a" và thuộc tính trong { method: "GET" } có thể được gán lại sau này, nên TypeScript mở rộng chúng thành string. Một biến const thì giữ literal type "a". Để giữ literal bên trong đối tượng, hãy chú thích literal type, dùng as const hoặc dùng satisfies.

const và as const khác nhau thế nào?

const là khai báo của JavaScript: biến không thể gán lại, nhưng đối tượng mà nó trỏ tới vẫn thay đổi được. as const là type assertion của TypeScript: nó khiến mọi thuộc tính của giá trị trở thành readonly và mang literal type trong hệ thống kiểu. Không cái nào đóng băng đối tượng lúc chạy; hãy dùng Object.freeze cho việc đó.

Làm sao để lấy union type từ một mảng chuỗi?

Khai báo mảng với as const, rồi truy cập kiểu của nó bằng number: const roles = ["admin", "user"] as const; type Role = (typeof roles)[number]; cho ra "admin" | "user". Không có as const, mảng có kiểu string[] và kết quả chỉ là string.

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu