TypeScript中如何在Observable<>泛型内动态使用类属性指定返回类型
通用服务代理类型动态化的正确实现路径
核心认知澄清
TypeScript的泛型系统是纯编译期的类型校验逻辑,编译完成后所有泛型标识会被完全擦除。你之前尝试写的
Observable<this.target>形式本质是混淆了运行时实例属性取值和编译期类型标识的边界,TS从语法规则上就不允许在泛型位置引用运行时的实例变量,eval类的运行时动态执行方案完全无法介入编译期的类型推导流程,从根源上没有落地可能。
你存储的target本质是DTO类的构造函数(不是普通值),完全可以通过标准泛型参数的方式实现类型和存储的目标类绑定,达到和预期一致的动态效果,不需要依赖任何特殊语法。
具体改造步骤
- 给类增加类级别的泛型参数,把返回结果的类型抽象为
TDto,替代硬编码的具体DTO类型 - 修正
target属性的类型定义:不要用any,而是声明为可实例化的类构造签名,和泛型TDto绑定 - 同步修正
setData方法的入参类型,保证传入的构造函数和泛型类型一致 - 把所有硬编码的DTO返回类型替换为泛型
TDto,让返回值类型跟随泛型自动推导
改造后的完整代码示例
// 增加类级泛型,TDto代表最终接口返回的结果类型 @Injectable() export class GenaricServiceProxy<TDto = any>{ private http: HttpClient; private baseUrl: string; protected jsonParseReviver: ((key: string, value: any) => any) | undefined = undefined; // target属性定义为可实例化的构造函数,和泛型TDto绑定 private target?: new (...args: any[]) => TDto; constructor(@Inject(HttpClient) http: HttpClient, @Optional() @Inject(API_BASE_URL) baseUrl?: string) { this.http = http; this.baseUrl = baseUrl ? baseUrl : ""; } // setData入参类型同步和target的构造函数类型匹配 public setData(target: new (...args: any[]) => TDto){ this.target = target; } // 返回值直接用泛型TDto,完全替代硬编码的WorkflowDtoPagedResultDto getAll(): Observable<TDto> { let url_ = this.baseUrl + "/api/services/app/Workflow/GetAll?"; let options_: any = { observe: "response", responseType: "blob", headers: new HttpHeaders({ "Accept": "text/plain" }) }; return this.http.request("get", url_, options_).pipe(_observableMergeMap((response_: any) => { return this.processGetAll(response_); })).pipe(_observableCatch((response_: any) => { if (response_ instanceof HttpResponseBase) { try { return this.processGetAll(<any>response_); } catch (e) { return <Observable<TDto>><any>_observableThrow(e); } } else return <Observable<TDto>><any>_observableThrow(response_); })) } // 解析方法的返回值也同步替换为TDto private processGetAll(response: HttpResponseBase): TDto { // 原有解析逻辑不变,如果需要把原始响应实例化为类对象,直接用存储的this.target构造即可 const rawResult = response.body; return this.target ? new this.target(rawResult) : rawResult as TDto; } }
实际使用方式
实例化服务的时候直接指定对应DTO的泛型,TS会自动完成全链路的类型推导,不需要手动硬编码返回值类型:
// 指定本次代理对应的DTO类型 const workflowProxy = new GenaricServiceProxy<WorkflowDtoPagedResultDto>(httpClient, baseUrl); // 传入对应的DTO构造函数,用于响应反序列化 workflowProxy.setData(WorkflowDtoPagedResultDto); // 调用getAll时,TS会自动识别返回值是Observable<WorkflowDtoPagedResultDto>,拥有完整的类型提示 workflowProxy.getAll().subscribe(res => { // res的类型完全匹配WorkflowDtoPagedResultDto,没有any类型丢失问题 console.log(res.totalCount); })
注意事项
- 类级别的泛型会在服务实例创建时固定绑定类型,不需要在方法调用时动态读取实例属性,完全符合TS的类型推导规则
- 如果没有给类指定具体泛型就实例化,TS会自动将泛型推导为默认的
any类型,和原有未抽象的效果完全一致 - 不要尝试通过运行时的类对象反向生成泛型,TS的类型系统不支持这种动态反向推导,硬写这类逻辑只会导致类型安全完全失效
内容的提问来源于stack exchange,提问作者Carsten Løvbo Andersen
相关产品推荐
相关产品推荐

