将JavaScript代码迁移至TypeScript时,动态为父类实例分配静态对象的类型定义生成问题
The core issue here is that TypeScript can't infer the instance properties (like reports) added dynamically in the Api constructor from the static endpoints property. We can fix this by using generics and mapped types to explicitly link the static endpoints type to the instance's properties.
Step-by-Step Fix
1. Update the Api Class with Generics
Modify the base Api class to accept a generic parameter representing the shape of its static endpoints. We'll use a mapped type to define the instance properties that correspond to each endpoint class in endpoints:
// api.ts class Api<Endpoints extends Record<string, typeof Endpoint>> { static baseUrl: string; static endpoints: Record<string, typeof Endpoint>; // Define instance properties mapped from the Endpoints generic [K in keyof Endpoints]: InstanceType<Endpoints[K]>; constructor() { const { baseUrl, endpoints } = this.constructor as typeof Api<Endpoints>; const endpointInstances = Object.entries(endpoints).reduce((instances, [name, EndpointClass]) => { instances[name as keyof Endpoints] = new EndpointClass(baseUrl) as InstanceType<Endpoints[keyof Endpoints]>; return instances; }, {} as { [K in keyof Endpoints]: InstanceType<Endpoints[K]> }); Object.assign(this, endpointInstances); } }
2. Update the Etp Subclass to Pass the Generic Parameter
When extending Api, pass the type of static endpoints to the generic parameter. This lets TypeScript infer exactly which instance properties should exist:
// etp/index.ts import Api from '../api'; import Reports from './reports'; class Etp extends Api<{ reports: typeof Reports }> { static baseUrl = '/planning/api/v1/'; static endpoints = { reports: Reports }; } export default new Etp();
3. Verify the Generated Type Definitions
With these changes, the generated etp/index.d.ts will automatically include the reports instance property as you expected:
// etp/index.d.ts import Api from '../api'; import Reports from './reports'; declare class Etp extends Api<{ reports: typeof Reports; }> { static baseUrl: string; static endpoints: { reports: typeof Reports; }; reports: InstanceType<typeof Reports>; } declare const _default: Etp; export default _default;
Why This Works
- Generics: The
Api<Endpoints>generic parameter captures the exact type of the staticendpointsproperty for each subclass. - Mapped Types:
[K in keyof Endpoints]: InstanceType<Endpoints[K]>tells TypeScript to create an instance property for every key inEndpoints, using the instance type of the corresponding endpoint class. - Type Assertions: We use type assertions in the constructor to ensure TypeScript recognizes that the dynamically created instances match the mapped type definition.
Additional Notes
- Make sure all your endpoint classes (like
Reports) extendEndpointcorrectly, soInstanceTypecan resolve their instance types properly. - If you have multiple endpoint subclasses, you don't need to change the
Apiclass further—just update the generic parameter when extendingApifor each subclass (e.g.,Api<{ reports: typeof Reports, users: typeof Users }>).
内容的提问来源于stack exchange,提问作者gautamits

