Tìm kiếm trang web

đệ quy - Tham chiếu hàm vòng tròn trong Typescript


Hàm này thực ra khá đơn giản, nó hoặc trả về một hàm hoặc một chuỗi tùy thuộc vào một tham số của hàm bên trong.

function strBuilder(str: string) {
  return function next(str2?: string) {
    if(typeof str2 === "string") {
      return strBuilder(str + str2)
    } else {
      return str
    }
  }
}

Tuy nhiên, có một lỗi kiểu khi biểu thức này được thực thi

strBuilder("Hello, ")("World")()

Tôi muốn biết cách chú thích hàm này một cách đúng đắn để loại bỏ lỗi kiểu vì việc suy luận kiểu không hoạt động tốt lắm.

Loại này là thứ mà tôi đang nghĩ đến nhưng tôi không thể tìm ra cách để nói với hàm rằng nó thuộc loại cụ thể đó.

type StringBuilder<S extends string> = (
  str: S
) => (str2?: S) => S extends string ? ReturnType<StringBuilder<S>> : string

Bất kỳ loại sự giúp đỡ nào cũng được trân trọng.

Đây là liên kết playground minh họa vấn đề https://www.typescriptlang.org/play?#code/C4TwDgpgBAysBOBLAdgcwEIFdEBsAmE8APDFBAB7ATJ4DOUtCKqAfFALxQAUAUFAwgBcsHgEoObLo3gAmAPzCY49m1IUqNetOZQ5UAEoRgmeMgAq4CCSZosuAsRgs2w7Wh48AZpmQBjYIgA9sgC8Hb4hFJCocziAN58UPBGJiHefgHBUMjqUbIKMWjxifyInlygkIGeoTIc7JwARG6ojcX8HUkppqHhDnlQANS1oiVQAL5kOLTQCZ38ycY90mPjiWtrPNJ9kY0AEhA4OIEANFBtXI0A6oHw+BejQA

Câu trả lời #1

Cách tiếp cận có lẽ dễ nhất ở đây là biểu thị kiểu trả về của strBuilder() dưới dạng một kiểu hàm nạp chồng:

interface StringBuilder {
  (str: string): StringBuilder;
  (): string;
}

function strBuilder(str: string): StringBuilder {
  return function next(str2?: string) {
    if (typeof str2 === "string") {
      return strBuilder(str + str2)
    } else {
      return str
    }
  } as StringBuilder // <-- assert
}

Chúng ta cần một khẳng định ở đó vì trình biên dịch không thể xác minh chính xác rằng các triển khai hàm phù hợp với các chữ ký gọi nạp chồng (xem Cách nạp chồng hàm đúng trong TypeScript?).

Dù sao, đoạn mã trên biên dịch được và cho kết quả hành vi gọi như mong muốn:

const str = strBuilder("Hello, ")("World")();
console.log(str.toUpperCase())

Bạn có thể viết nó dưới dạng loại điều kiện tổng quát, nhưng điều đó phức tạp hơn và không nhất thiết là mong muốn hơn (thực sự phụ thuộc vào các trường hợp sử dụng):

interface StringBuilder {
  <S extends string | undefined = undefined>(str?: S):
     S extends string ? StringBuilder : string;
}

Điều đó tương tự với phiên bản của bạn, ngoại trừ việc tôi chỉ đại diện trực tiếp cho kiểu trả về (để không sử dụng ReturnType và để không phải lo lắng về việc sử dụng cùng S trong hàm gốc và trong hàm trả về, điều mà bạn không muốn) và tôi đang đặt mặc định cho S trong trường hợp ai đó gọi hàm mà không có đối số (nếu không, nó sẽ chuyển về string | undefined mà điều này không hữu ích).

Bạn vẫn cần một phép khẳng định kiểu bên trong thân của strBuilder() vì trình biên dịch cũng không thể xác minh chính xác rằng các triển khai hàm tuân theo các kiểu trả về điều kiện tổng quát; xem microsoft/TypeScript#33912. Vì vậy, không phải là bạn được hay mất gì về mặt an toàn kiểu ở đây.

Dù sao thì nó cũng hoạt động giống nhau với ví dụ trên:

const str = strBuilder("Hello, ")("World")();
console.log(str.toUpperCase())

Tôi có lẽ sẽ giữ nguyên các overload trừ khi bạn gặp phải một số vấn đề cụ thể với nó.

Liên kết Playground đến mã