Tìm kiếm trang web

TypeScript đang suy luận ra kiểu never khi một hàm dự đoán kiểu trả về false


Tôi đang sử dụng một hàm trả về một predicate kiểu để thông báo cho TypeScript rằng một nguồn chắc chắn có hoặc không có thuộc tính media. Nếu nó là một SourceWithoutMedia, nó nên được xử lý theo một cách, và nếu là một SourceWithMedia, nó nên được xử lý theo cách khác.

Vấn đề là TypeScript suy luận từ trường hợp mà mệnh đề kiểu trả về false.

Tôi mong đợi TypeScript suy ra rằng nếu isSourceWithoutMedia(source) trả về false, thì source là một SourceWithMedia.

Điều nó thực sự suy ra là nếu isSourceWithoutMedia(source) trả về false, thì loại của source là never.

Tôi đang thiếu gì ở đây, và có cách nào để TypeScript suy luận rằng nếu isSourceWithoutMedia(source) trả về false, thì source là một SourceWithMedia không?

interface SourceWithMedia {
    id: string
    someOtherProperty: string
    media?: string
}

interface SourceWithoutMedia {
    id: string
}

const isSourceWithoutMedia = (source: SourceWithMedia | SourceWithoutMedia): source is SourceWithoutMedia => {
    return Object.keys(source).length === 1
}

function doSomethingWithMedia (source: SourceWithMedia | SourceWithoutMedia) {
    if (isSourceWithoutMedia(source)) return source
    
    if (source.media) return source.media // Error: Property 'media' does not exist on type 'never'.
    
}

Ví dụ trên sân chơi TypeScript

Câu trả lời #1

...nếu isSourceWithoutMedia(source) trả về false, thì source là một SourceWithMedia?

Không.

Vấn đề là SourceWithMedia mở rộng SourceWithoutMedia tức là SourceWithMedia là một kiểu phụ của SourceWithoutMedia.

const withMedia: SourceWithMedia = {id: "", someOtherProperty: "", media: ""}
const withoutMedia = withMedia; // valid

Khi cố gắng thu hẹp source thành SourceWithoutMedia bằng cách sử dụng predicate đó, thực ra không có gì xảy ra. source vẫn sẽ có kiểu hợp nhất, ngay cả khi bạn sử dụng một cái gì đó như !("media" in source) trong predicate của bạn hoặc kiểm tra xem tham số media có bị thiếu trước như sau:

function doSomethingWithMedia(source: SourceWithMedia | SourceWithoutMedia) {
  if (isSourceWithoutMedia(source)) {
    source;
    //^? (parameter) source: SourceWithMedia | SourceWithoutMedia
  }
}

Bây giờ, nếu chúng ta theo nhánh âm của câu lệnh if, sẽ không còn thành viên hợp nhất nào để thu hẹp. TypeScript biểu thị các hợp nhất rỗng bằng kiểu never.


Mặt khác, nếu bạn đảo ngược logic mệnh đề của mình và kiểm tra xem thuộc tính media có ở đó một cách rõ ràng hay không, bạn sẽ có thể thu hẹp source thành SourceWithMedia. Bằng cách đó, TypeScript thực sự có thể phân biệt giữa hai loại và cho biết rằng chúng ta đang xử lý một giá trị thuộc loại SourceWithMedia vì SourceWithoutMedia không thể gán cho SourceWithMedia.

Sân chơi TypeScript

Câu trả lời #2

TypeScript không hiểu mối quan hệ giữa khối điều kiện đầu tiên ở đây và khối thứ hai.

Một giải pháp cho việc này là ép kiểu rõ ràng nguồn sang SourceWithMedia.

function doSomethingWithMedia (source: SourceWithMedia | SourceWithoutMedia) {
  if (isSourceWithoutMedia(source)) return source

  const sourceWithMedia = source as SourceWithMedia
  if (sourceWithMedia.media) return sourceWithMedia.media;
}