value as Type là một type assertion: nó bảo TypeScript coi value là Type. Mọi người hay gọi nó là ép kiểu (cast), nhưng thực ra nó chỉ là một chỉ dẫn cho trình biên dịch. Nó bị xóa khỏi đầu ra JavaScript, không chuyển đổi gì và không kiểm tra gì lúc chạy.
Đây là cách dùng điển hình: bạn biết về một giá trị nhiều hơn trình biên dịch (ở đây là cấu trúc của một đoạn JSON), và bạn nói ra điều đó. Nếu bạn sai, sẽ không có gì cảnh báo. Các phần tiếp theo cho thấy điều đó nghĩa là gì và khi nào một phép kiểm tra lúc chạy là lựa chọn tốt hơn.
as và cú pháp ngoặc nhọn
Cùng một assertion có hai cách viết:
const someValue: unknown = "hello";
const a = someValue as string; // as syntax
const b = <string>someValue; // angle-bracket syntax, same meaning
Dạng ngoặc nhọn không được phép trong tệp .tsx, nơi <string> sẽ bị đọc thành một thẻ JSX. Dùng as ở mọi nơi thì bạn không bao giờ phải bận tâm. Assertion có độ ưu tiên thấp, nên hãy bọc nó trong ngoặc đơn khi bạn viết tiếp biểu thức: (value as string).length.
Assertion không chuyển đổi giá trị
Đây là phần gây ra bug thật. Một assertion thay đổi điều trình biên dịch tin về một giá trị, chứ không thay đổi chính giá trị đó:
Trình biên dịch tin asserted là number, nên asserted + 1 qua được kiểm tra kiểu như một phép cộng số. Lúc chạy, nó vẫn là chuỗi "42" và JavaScript nối chuỗi. Để đổi kiểu của một giá trị, hãy chuyển đổi nó: Number(x), String(x), Boolean(x), BigInt(x), new Date(x). Trang chuyển chuỗi sang số so sánh các hàm chuyển đổi.
| Bạn muốn | Viết | Tác dụng lúc chạy |
|---|---|---|
| Cho trình biên dịch biết một kiểu bạn đã biết | x as T | không có |
| Biến chuỗi thành số | Number(x), parseInt(x, 10) | chuyển đổi |
| Biến mọi thứ thành chuỗi | String(x), `${x}` | chuyển đổi |
| Kiểm tra kiểu trước | một type guard, typeof, instanceof | kiểm tra |
Trình biên dịch cho phép những gì
as không phải không có giới hạn. TypeScript cho phép x as T khi một trong hai kiểu gán được cho kiểu kia: mở rộng ("a" as string, dog as Animal) và thu hẹp (animal as Dog, unknown as User) đều được. Khi hai kiểu hoàn toàn không giao nhau, nó từ chối:
Trình biên dịch báo index.ts(3,11): error TS2352: Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first. Chính thông báo này chỉ ra lối thoát: input as unknown as number. Assertion kép đó biên dịch được, và lúc chạy nó sai y hệt ví dụ ở trên. Khi bạn thấy cần đến nó, cách sửa đúng thường là một phép chuyển đổi (Number(input)) hoặc một kiểu khác.
Quy tắc giao nhau khá lỏng với đối tượng. Một object literal có một vài thuộc tính vẫn được chấp nhận, và đó là cách as âm thầm cho những đối tượng thiếu sót lọt qua:
Một chú thích kiểu (const draft: User = { name: "Ada" }) hoặc satisfies User sẽ báo thiếu email (TS2741). Chỉ dùng as trên object literal khi bạn thật sự định điền đủ sau này, và nên ưu tiên tạo đối tượng đầy đủ ngay từ đầu.
as const thì khác
as const trông giống một assertion nhưng làm điều ngược lại với nới lỏng: nó làm một literal hẹp nhất có thể. Chuỗi giữ nguyên literal type, mảng thành tuple readonly, và thuộc tính của đối tượng thành readonly.
Cách này an toàn, vì nó mô tả literal một cách chính xác thay vì khẳng định điều mà trình biên dịch không thấy được. (sizes as readonly string[] bên trong isSize là một assertion mở rộng, cũng an toàn: nó cho phép includes nhận bất kỳ chuỗi nào.) Xem thêm ở trang literal type.
Khi nào type guard là công cụ tốt hơn
as là một lời khẳng định; type guard là một phép kiểm tra. Ở ranh giới nơi dữ liệu đến từ bên ngoài code của bạn (JSON, fetch, localStorage, dữ liệu người dùng nhập, một message), lời khẳng định có thể sai, và một assertion biến một lỗi rõ ràng ở ranh giới thành một lỗi khó hiểu ở chỗ khác.
Hướng dẫn sơ lược về những công cụ trông giống nhau:
| Công cụ | Kiểm tra lúc biên dịch | Kiểm tra lúc chạy | Dùng khi |
|---|---|---|---|
Chú thích kiểu const x: T = ... | có, đầy đủ | không | bạn tự tạo giá trị |
satisfies T | có, đầy đủ, giữ kiểu được suy ra | không | object literal, cấu hình |
as T | chỉ xem "hai kiểu có giao nhau không" | không | bạn biết nhiều hơn trình biên dịch |
x! | chỉ loại bỏ null / undefined | không | bạn biết giá trị đã được gán |
Type guard x is T | thân của guard là code bình thường | có | dữ liệu từ bên ngoài |
Vẫn còn hai cách dùng as hợp lý: thu hẹp kiểu ở chỗ trình biên dịch không theo dõi được (một mục Map bạn vừa đặt hai dòng trước, một giá trị từ thư viện không có kiểu), và code test tạo dữ liệu mẫu không đầy đủ. Hãy giữ chúng ngắn gọn và đặt gần nơi bạn biết lời khẳng định là đúng.
Câu hỏi thường gặp
as làm gì trong TypeScript?
value as Type là một type assertion: nó bảo trình biên dịch coi value là Type từ đây trở đi. Nó bị xóa khỏi JavaScript sau khi biên dịch, nên không chuyển đổi gì và không kiểm tra gì lúc chạy. Nếu assertion sai, chương trình sẽ hỏng về sau, ở bất cứ chỗ nào kiểu sai đó được dùng.
Làm sao để ép kiểu trong TypeScript?
TypeScript không có ép kiểu lúc chạy. Dùng as (hoặc cách cũ <Type>value) để đổi kiểu tĩnh khi bạn biết nhiều hơn trình biên dịch. Để thật sự chuyển đổi một giá trị, hãy gọi một hàm: Number("42"), String(42), Boolean(x), new Date(text).
"as unknown as" trong TypeScript nghĩa là gì?
Đó là một assertion kép. TypeScript từ chối x as T khi hai kiểu hoàn toàn không giao nhau (lỗi TS2352), và đi qua unknown trước sẽ vượt qua phép kiểm tra đó, vì mọi thứ đều có thể được assert thành unknown và ngược lại. Cách này tắt hẳn việc kiểm tra kiểu cho giá trị đó, nên chỉ dùng trong test và trong code mà bạn đã xác minh kiểu bằng cách khác.
as và dấu ngoặc nhọn trong TypeScript khác nhau thế nào?
Không khác về ý nghĩa: <string>value và value as string là cùng một assertion. Dạng ngoặc nhọn không dùng được trong tệp .tsx vì xung đột với JSX, nên as là dạng mà ai cũng dùng.
as và satisfies khác nhau thế nào?
as ghi đè kiểu được suy ra và kiểm tra rất ít (thiếu thuộc tính vẫn được chấp nhận). satisfies kiểm tra giá trị theo một kiểu, báo thuộc tính thiếu hoặc thừa, và giữ nguyên kiểu được suy ra chính xác. Hãy ưu tiên satisfies cho object literal và chỉ dùng as khi bạn thật sự biết nhiều hơn trình biên dịch.