Menu

as trong TypeScript: type assertion và ép kiểu

Từ khóa as bảo TypeScript coi một giá trị là một kiểu khác. Đây không phải ép kiểu: không có gì được chuyển đổi hay kiểm tra lúc chạy. Tìm hiểu cú pháp as và dấu ngoặc nhọn, những gì trình biên dịch cho phép, assertion kép qua unknown, và khi nào type guard là công cụ tốt hơn.

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.

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ốnViếtTác dụng lúc chạy
Cho trình biên dịch biết một kiểu bạn đã biếtx as Tkhô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ỗiString(x), `${x}`chuyển đổi
Kiểm tra kiểu trướcmột type guard, typeof, instanceofkiể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ịchKiểm tra lúc chạyDùng khi
Chú thích kiểu const x: T = ...có, đầy đủkhôngbạn tự tạo giá trị
satisfies Tcó, đầy đủ, giữ kiểu được suy rakhôngobject literal, cấu hình
as Tchỉ xem "hai kiểu có giao nhau không"khôngbạn biết nhiều hơn trình biên dịch
x!chỉ loại bỏ null / undefinedkhôngbạn biết giá trị đã được gán
Type guard x is Tthân của guard là code bình thườngcó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.

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