Tìm kiếm trang web

typescript - Tại sao việc thêm một ràng buộc trên một loại tổng quát lại thay đổi hành vi suy luận?


Hãy xem xét ví dụ sau:

function identity<T>(arr: T[]) { return arr }

identity(["a", "b"])

Ở đây, kiểu tổng quát T đang được suy luận là một string, điều này có ý nghĩa với tôi.

Nhưng nếu một ràng buộc được thêm vào T (như được hiển thị bên dưới), thì T được suy ra là "a" | "b", tại sao?

function identity<T extends string>(arr: T[]) { return arr }

identity(["a", "b"])

Câu trả lời #1

Tôi đã học được vài điều để chia sẻ. Đây không phải là câu trả lời cuối cùng nhưng đáng để lưu ý:

Khi nào một tham số kiểu được mở rộng, từ "a" thành string?

https://github.com/microsoft/TypeScript/pull/10676

Trong quá trình suy luận tham số kiểu cho một biểu thức gọi, kiểu được suy luận cho tham số kiểu T sẽ được mở rộng thành kiểu literal mở rộng của nó nếu:

  • tất cả các suy luận cho T được thực hiện đối với các xuất hiện cấp cao của T trong loại tham số cụ thể, và
  • T không có ràng buộc hoặc ràng buộc của nó không bao gồm các kiểu nguyên thủy hoặc kiểu hằng số, và
  • T đã được cố định trong quá trình suy luận hoặc T không xuất hiện ở cấp cao nhất trong kiểu trả về.

Tham số kiểu T được mở rộng nếu:

  • T không có ràng buộc
  • hoặc ràng buộc của nó không bao gồm các kiểu nguyên thủy hoặc kiểu hằng

Mở rộng
<T>(arr:T[]) không có ràng buộc, do đó các kiểu sẽ được mở rộng. 'a'=string

Phiên bản không bị ràng buộc thì rất chung chung, TypeScript không đưa ra quá nhiều giả định về cách xử lý các hằng.

Không được Mở Rộng
<T extends string>(arr:T[]) có một ràng buộc nguyên thủy và không được mở rộng.

Vì phiên bản bị giới hạn ít tổng quát hơn, nó giữ lại chúng. Xem PR để hiểu về việc nói chung muốn giữ lại các hằng số càng nhiều càng tốt.

Đây cũng là một mẩu thông tin thú vị:

Trong các trường hợp mà các kiểu literal được giữ nguyên trong suy luận cho một tham số kiểu (tức là khi không xảy ra việc mở rộng kiểu), nếu tất cả các suy luận đều là các kiểu literal hoặc các kiểu hợp literal có cùng kiểu nguyên thủy cơ bản, kiểu suy luận kết quả là một hợp của các suy luận. Ngược lại, kiểu suy luận là kiểu siêu chung của các suy luận, và sẽ xảy ra lỗi nếu không có kiểu siêu chung như vậy.

Tất cả các suy luận của T đều là các kiểu chữ viết tường minh ("a", "b") của cùng một kiểu nguyên thủy cơ sở (string).

const fn = <T extends string>(arr:T[]) => arr
fn(['a', 'b'])  // ('a' | 'b')[]

Nếu suy luận không phải là kiểu từ ngữ, kiểu được suy luận là siêu kiểu chung, string.

const fn = <T extends string>(arr:T[]) => arr
fn(['a' as string, "b"]) // widened to common supertype `string` 

Nói chung, TS sẽ suy luận kiểu "tốt nhất" cho một đầu vào nhất định. Hàm này tình cờ truyền các hằng chuỗi (như một biểu thức không thể thay đổi) tới identity, mà kiểu trả về của nó là T[] với T bị hạn chế bởi string. Nếu có thể giữ nguyên các hằng, nó sẽ làm như vậy.

Bạn có thể thấy có một đại dương các kịch bản (được dán từ PR đó) nơi suy luận có thể giữ nguyên các giá trị nguyên gốc, và nơi nó không thể.

  • https://github.com/microsoft/TypeScript/pull/10676
  • https://github.com/microsoft/TypeScript/pull/11126

Tôi không có câu trả lời về việc kết hợp các quy tắc suy luận nào cụ thể cho phép hành vi này, nhưng nói chung, TypeScript thì sẽ là TypeScript, và nói chung cố gắng tạo ra kết quả mà chúng ta muốn.

Bài viết liên quan