Menu

satisfies trong TypeScript: so với chú thích kiểu và as

Toán tử satisfies kiểm tra một giá trị có khớp với một kiểu hay không mà không thay đổi kiểu được suy luận của giá trị đó. Tìm hiểu satisfies làm gì, so sánh nó với chú thích kiểu và với as (cùng một đối tượng viết theo ba cách), cách kết hợp với as const, và vì sao nó hợp với đối tượng cấu hình.

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 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 = vAssertion v as Tv satisfies T
Thiếu thuộc tínhlỗicho phéplỗi
Thừa thuộc tính (object literal)lỗicho phéplỗi
Sai kiểu thuộc tínhlỗichỉ khi hai kiểu không giao nhaulỗi
Kiểu của x sau đóTTkiểu được suy luận của v
Literal type ("dark", 8080)mở rộng thành Tmở rộng thành Tgiữ 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ạykhôngkhôngkhô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 } cho cfg kiểu { port: number }, nên lệnh cfg = { 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. satisfies phá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ợ.

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