value satisfies Type kiểm tra lúc biên dịch rằng value khớp với Type, rồi để nguyên kiểu riêng, chính xác hơn, của giá trị đó. Một chú thích kiểu sẽ thay kiểu chính xác đó bằng Type; satisfies kiểm tra mà không mở rộng kiểu.
satisfies vẫn làm phần kiểm tra: thiếu một màu, gõ sai khóa thành bleu, hoặc một giá trị như true đều là lỗi biên dịch ngay trên dòng đó. Nó có từ TypeScript 4.9 và, giống mọi chú thích kiểu, bị xóa khỏi JavaScript được sinh ra.
Vấn đề mà satisfies giải quyết
Với chú thích kiểu, kiểu của biến chính là chú thích đó. Trình biên dịch quên những gì nó thấy trong literal. Ở đây cùng bảng màu đó được chú thích kiểu, và giờ TypeScript không còn biết green là một chuỗi:
Trình biên dịch báo:
index.ts(11,27): error TS2339: Property 'toUpperCase' does not exist on type 'Color'.
Property 'toUpperCase' does not exist on type '[number, number, number]'.
Trước TypeScript 4.9, bạn chỉ có hai lựa chọn: chú thích kiểu rồi tự thu hẹp kiểu ở khắp nơi (typeof palette.green === "string"), hoặc bỏ chú thích và mất phần kiểm tra. satisfies cho bạn cả hai. Đổi : Record<ColorName, Color> thành satisfies Record<ColorName, Color> sau dấu ngoặc nhọn đóng là code chạy được.
satisfies so với chú thích kiểu và as
Cùng một đối tượng cài đặt, viết theo ba cách:
as cho phép thiếu lang, và asserted.lang là undefined lúc chạy trong khi kiểu của nó nói là string. Bỏ lang khỏi hai dòng còn lại thì cả hai đều báo lỗi TS2741, Property 'lang' is missing in type ....
Chú thích kiểu const x: T = v | Assertion v as T | v satisfies T | |
|---|---|---|---|
| Thiếu thuộc tính | lỗi | cho phép | lỗi |
| Thừa thuộc tính (object literal) | lỗi | cho phép | lỗi |
| Sai kiểu thuộc tính | lỗi | chỉ khi hai kiểu không giao nhau | lỗi |
Kiểu của x sau đó | T | T | kiểu được suy luận của v |
Literal type ("dark", 8080) | mở rộng thành T | mở rộng thành T | giữ lại nếu T cho phép |
Khóa của Record<string, ...> | chuỗi bất kỳ (gõ sai vẫn biên dịch) | chuỗi bất kỳ | đúng các khóa đã viết |
| Ảnh hưởng lúc chạy | không | không | không |
Quy tắc chung: dùng chú thích kiểu khi bạn muốn biến mang kiểu đã khai báo (một giá trị bạn sẽ gán lại, một API công khai), và dùng satisfies khi bạn muốn được kiểm tra nhưng kiểu riêng của giá trị hữu ích hơn.
Bắt lỗi trong object literal
satisfies chạy toàn bộ phép kiểm tra khả năng gán, kể cả kiểm tra thuộc tính thừa, nên gõ sai tên khóa là lỗi:
type Route = { path: string; method: "GET" | "POST" };
const home = { path: "/", metod: "GET" } satisfies Route;
// error TS2561: Object literal may only specify known properties, but 'metod' does not exist in type 'Route'. Did you mean to write 'method'?
Phép kiểm tra này cũng cho literal một kiểu ngữ cảnh (contextual type), giống hệt chú thích kiểu. Điều đó quan trọng theo hai cách. Chuỗi literal được giữ là literal type khi kiểu đích cần chúng: { path: "/", method: "GET" } satisfies Route có method: "GET", trong khi cùng đối tượng đó không có chú thích sẽ được suy luận là method: string. Và tham số của callback được suy luận từ kiểu đích:
Khóa của Record vẫn được biết rõ
Một cách dùng phổ biến là bảng tra cứu. Khi chú thích kiểu là Record<string, T>, mọi chuỗi đều là khóa hợp lệ và một lỗi gõ sai vẫn biên dịch được, rồi trả về undefined lúc chạy. Với satisfies, các giá trị vẫn được kiểm tra với T, nhưng kiểu của biến liệt kê đúng những khóa bạn đã viết:
keyof typeof endpoints chỉ hữu ích vì các khóa vẫn còn. Với chú thích kiểu, nó sẽ chỉ là string.
Để bắt buộc một tập khóa cố định, hãy dùng satisfies với một Record trên một union: satisfies Record<"dev" | "prod", string> báo thiếu prod bằng TS2741 và báo khóa lạ staging bằng TS2353.
as const satisfies
as const và satisfies kết hợp được với nhau. Viết as const trước: nó khiến giá trị trở thành readonly ở mọi cấp với literal type, rồi satisfies kiểm tra đúng giá trị đó.
Mỗi route được kiểm tra với Route (một method: "PUT" sẽ là lỗi), và tuple các literal type vẫn dùng được, nên Path là union của các đường dẫn thật. Dùng readonly Route[] (hoặc ReadonlyArray<Route>) làm kiểu đích, vì một mảng as const là readonly.
Đối tượng cấu hình
Cấu hình là nơi satisfies phát huy tác dụng: cấu trúc phải đúng, và code ở chỗ khác cần các giá trị chính xác.
Quên mục production, gõ sai logLevel, hoặc viết logLevel: "verbose", trình biên dịch sẽ chỉ ra đúng dòng đó. Cách làm này cũng hợp với các tệp *.config.ts: export default { ... } satisfies SomeConfig kiểm tra cả tệp trong khi đối tượng được export vẫn giữ các giá trị literal của nó.
Khi nào không nên dùng satisfies
- Biến sẽ được gán lại.
let cfg = { port: 3000 } satisfies { port: number | string }chocfgkiểu{ port: number }, nên lệnhcfg = { port: "80" }sau đó sẽ lỗi (TS2322). Hãy chú thích kiểu cho những biến bạn định thay đổi. - Bạn cố ý muốn dùng kiểu đã khai báo. Với giá trị trả về của hàm hay một hằng số được export thuộc về API, kiểu của chú thích chính là hợp đồng, và để lộ literal type chính xác có thể khiến những thay đổi sau này gây breaking change.
- Giá trị không phải là literal.
satisfiesphát huy tốt nhất trên object literal và array literal. Với một biến hay kết quả của một lời gọi hàm, nó chỉ là phép kiểm tra khả năng gán thông thường, thứ mà chú thích kiểu đã cho bạn rồi.
Câu hỏi thường gặp
satisfies trong TypeScript làm gì?
expression satisfies Type kiểm tra lúc biên dịch rằng biểu thức gán được cho Type, báo lỗi khi thiếu thuộc tính, thừa thuộc tính hay sai kiểu giá trị, rồi giữ nguyên kiểu được suy luận của chính biểu thức đó. Bạn có cả sự an toàn của chú thích kiểu lẫn độ chính xác của suy luận kiểu. Nó bị xóa khỏi đầu ra JavaScript.
satisfies khác gì chú thích kiểu?
Cả hai đều kiểm tra giá trị. Chú thích kiểu (const x: T = ...) sau đó gán cho biến kiểu T, quên đi những gì trình biên dịch đã biết về giá trị (literal type, mỗi thuộc tính thuộc thành viên nào của union, những khóa nào tồn tại). satisfies T giữ kiểu được suy luận, nên x.someKey được biết là có tồn tại, và một thuộc tính string | number đang chứa chuỗi sẽ có kiểu string.
satisfies và as trong TypeScript khác nhau thế nào?
as là một assertion: nó ghi đè kiểu và gần như không kiểm tra gì, nên thiếu thuộc tính cũng không ai phát hiện. satisfies là một phép kiểm tra: giá trị phải thật sự khớp với kiểu, và kiểu được suy luận của nó được giữ lại. Khi cả hai đều biên dịch được, satisfies là lựa chọn an toàn hơn.
as const satisfies nghĩa là gì?
Nó áp dụng cả hai: as const khiến giá trị trở thành readonly ở mọi cấp với literal type, rồi satisfies kiểm tra kết quả đó với một kiểu. Viết as const trước: const routes = [...] as const satisfies readonly Route[];. Biến giữ đúng các literal type để dùng sau, và một phần tử sai vẫn là lỗi biên dịch.
satisfies có từ phiên bản TypeScript nào?
TypeScript 4.9, phát hành tháng 11 năm 2022. Đây là cú pháp xóa được thông thường, nên nó cũng chạy được với type stripping có sẵn của Node, và mọi phiên bản TypeScript hiện nay (kể cả 7) đều hỗ trợ.