typescript - Làm thế nào để mở rộng giao diện với overload cụ thể khi nó có một catch all?
Tôi đang cố gắng thêm một chữ ký phương thức nạp chồng trong một interface từ một gói npm mà định nghĩa nhiều chữ ký cụ thể rồi sau đó là một chữ ký tổng quát, nhưng chữ ký nạp chồng của tôi không được nhận diện khi tôi viết một lời gọi đáng lẽ khớp với nó; thay vào đó chữ ký tổng quát lại được nhận diện. Có vẻ như một vấn đề liên quan đến thứ tự (mặc dù tất nhiên tôi có thể sai), như thể chữ ký tổng quát đang được kiểm tra với lời gọi trước chữ ký nạp chồng mới của tôi và vì khớp nên đang được sử dụng. Làm thế nào để chữ ký nạp chồng của tôi được nhận diện?
- Tôi đã đảm bảo rằng tôi đang mở rộng giao diện đúng.
- Tôi đã "chứng minh" rằng phần bao quát chung là vấn đề bằng cách chú thích nó trong các kiểu của module gốc — khi tôi làm vậy, phần nạp thêm của tôi được nhận diện nơi nó trước đây không được. (Và sau đó nếu tôi cũng chú thích phần nạp thêm của mình, cuộc gọi sẽ hiển thị lỗi vì không có gì phù hợp.)
- Tôi đã kiểm tra và chắc chắn rằng mình không đạt đến số lượng quá tải tối đa. Chỉ có sáu bản gốc, cộng với phần bổ sung của tôi sẽ là bảy; một kiểm tra nhanh cho thấy giới hạn cao hơn nhiều so với con số đó.
- Tôi đã kiểm tra rằng nếu tôi tạm thời bỏ qua tệp
.d.tscủa mình và thêm quá tải mới trực tiếp vào giao diện gốc, phía trên phần catch-all, nó sẽ hoạt động. (Cảm ơn gợi ý đó, Eldar) - Tôi đã thử tham chiếu tệp
.d.tscủa mình một cách rõ ràng trongtsconfig.json(mặc dù tôi nghi ngờ rằng điều đó không hợp lý). - Tôi đã thử tham chiếu tệp
.d.tscủa mình thông qua chỉ thị tham chiếu ba gạch chéo (mặc dù tôi nghi ngờ điều đó không hợp lý). - Tôi đã thử cả hai cách trên với một
.tsđơn giản thay vào đó (mặc dù tôi nghi ngờ... 😊). - Tôi đã xem qua nhiều câu hỏi về việc bổ sung giao diện (1, 2, 3, 4 và những câu khác), nhưng không thể hiểu được dựa trên chúng.
Đây được coi là một câu hỏi chung, nhưng một ví dụ cụ thể sẽ giúp tập trung câu hỏi và các câu trả lời có thể: Trường hợp cụ thể là phương thức extend của lodash.
Trong @types/lodash/common/object.d.ts, extend được định nghĩa như sau:
declare module "../index" {
// ...lots of other stuff...
interface LoDashStatic {
/**
* @see _.extend
*/
extend<TObject, TSource>(object: TObject, source: TSource): TObject & TSource;
/**
* @see _.extend
*/
extend<TObject, TSource1, TSource2>(object: TObject, source1: TSource1, source2: TSource2): TObject & TSource1 & TSource2;
/**
* @see _.extend
*/
extend<TObject, TSource1, TSource2, TSource3>(object: TObject, source1: TSource1, source2: TSource2, source3: TSource3): TObject & TSource1 & TSource2 & TSource3;
/**
* @see _.extend
*/
extend<TObject, TSource1, TSource2, TSource3, TSource4>(object: TObject, source1: TSource1, source2: TSource2, source3: TSource3, source4: TSource4): TObject & TSource1 & TSource2 & TSource3 & TSource4;
/**
* @see _.extend
*/
extend<TObject>(object: TObject): TObject;
/**
* @see _.extend
*/
extend<TResult>(object: any, ...otherArgs: any[]): TResult;
}
// ...lots of other stuff...
}
Như bạn có thể thấy, có nhiều phiên bản nạp chồng với số lượng đối tượng nguồn cụ thể, và sau đó là một phiên bản 'bắt tất cả' ở cuối cùng sử dụng cú pháp rest.
Thông qua những mưu mẹo trong @types/lodash/index.d.ts mà tôi thẳng thắn mà nói là không hiểu, rõ ràng là giao diện LoDashStatic kết thúc trong không gian tên _, nơi mà chúng ta thấy nó khi sử dụng.
Tôi muốn thêm một phương thức quá tải nhận năm đối tượng nguồn thay vì chỉ tối đa bốn (như một ví dụ cho câu trả lời của tôi cho câu hỏi này). Vì vậy, trong một tệp lodash-extensions.d.ts, tôi đã làm như sau:
declare namespace _ {
export interface LoDashStatic {
tjc(): number; // ** I'll explain this below
/**
* @see _.extend
*/
extend<TObject, TSource1, TSource2, TSource3, TSource4, TSource5>(
object: TObject,
source1: TSource1,
source2: TSource2,
source3: TSource3,
source4: TSource4,
source5: TSource5
): TObject & TSource1 & TSource2 & TSource3 & TSource4 & TSource5;
}
}
Sau đó trong test.ts, tôi có:
import { extend, tjc } from "lodash";
const n = tjc();
console.log(n);
const fourSource = extend({ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 }, { e: 5 });
console.log(fourSource);
const fiveSource = extend({ a: 1 }, { b: 2 }, { c: 3 }, { d: 4 }, { e: 5 }, { f: 6 });
console.log(fiveSource);
Mã tjc ở đó và trong lodash-extensions.d.ts của tôi chỉ để kiểm tra rằng tôi đang bổ sung đúng giao diện. Dường như đúng như vậy, vì TypeScript chấp nhận việc nhập khẩu và kiểu của n là number. (Tất nhiên, nếu tôi thực sự chạy đoạn mã này, nó sẽ không hoạt động — tôi chưa triển khai tjc ở đâu cả.)
Trong phần trên, kiểu của fourSource là như bạn mong đợi, nó đang lấy phần nạp chồng bốn nguồn từ các kiểu của lodash và vì vậy kiểu của fourSource là một hợp loại của những kiểu đối tượng nguồn đó (và {} mục tiêu). Kiểu của fiveSource là unknown, mặc dù, vì (rõ ràng) nó lấy phần extend bao quát ở cuối định nghĩa của module gốc, chứ không phải phần nạp chồng mà tôi thêm, và vì tôi chưa cung cấp đối số kiểu cho TResult nên nó phải bỏ qua suy diễn.
Nếu tôi vào @types/lodash/common/object.d.ts và chú thích phần catch-all, overload mới của tôi sẽ được nhận một cách ổn định. Đây là "bằng chứng" của tôi (dù nó là gì đi nữa) rằng catch-all đang gây cản trở.
Tôi không thể nghĩ ra cách nào để thiết lập điều này trong TypeScript playground (hoặc tất nhiên, trên Stack Snippets tại chỗ), nhưng tôi đã tạo một stackblitz với mã ở trên, hoặc tất nhiên nếu bạn tạo một dự án với TypeScript, cài đặt lodash và @types/lodash, sau đó thả các tệp lodash-extensions.d.ts và test.ts ở trên vào, nó nên tái hiện vấn đề cục bộ.
Tôi có thể nghĩ ra một số cách giải quyết tạm thời, như khai báo extends của riêng tôi mà thực ra chỉ là tham chiếu đến lodash và chỉ định các overload. Nhưng có thể thêm overload của riêng tôi để nó được nhận diện khi sử dụng extends không, và nếu có, thì làm thế nào?
Câu trả lời #1
Tài liệu về mở rộng module nói rằng bạn nên sử dụng declare module cho mục đích này:
declare module 'lodash' {
export interface LoDashStatic {
extend<TObject, TSource1, TSource2, TSource3, TSource4, TSource5>(
object: TObject,
source1: TSource1,
source2: TSource2,
source3: TSource3,
source4: TSource4,
source5: TSource5
): TObject & TSource1 & TSource2 & TSource3 & TSource4 & TSource5;
}
}
Điều đó sẽ hoạt động rất tốt nếu bạn đặt nó trong cùng một tệp mà bạn sử dụng lodash. Nhưng nếu bạn muốn đặt nó trong một tệp riêng, thì bạn sẽ gặp phải vấn đề khó chịu là trình biên dịch sẽ hiểu nó như một module ambient mới thay vì sửa đổi một module đã được import. Xem microsoft/TypeScript#49227. Vì vậy, nếu bạn muốn làm điều này trong một tệp riêng, tệp đó nên có ít nhất một import hoặc export ở cấp cao nhất:
import 'lodash'; // or whatever
declare module 'lodash' {
export interface LoDashStatic {
extend<TObject, TSource1, TSource2, TSource3, TSource4, TSource5>(
object: TObject,
source1: TSource1,
source2: TSource2,
source3: TSource3,
source4: TSource4,
source5: TSource5
): TObject & TSource1 & TSource2 & TSource3 & TSource4 & TSource5;
}
}
Bạn có thể thấy điều này hoạt động trong phiên bản dự án stackblitz này.
