使用RxJS构建TypeScript可观察类时,嵌套可观察对象出现类型错误的问题排查
看起来你已经把「让整个类成为可观察对象」的思路走通了大半,但嵌套的可观察对象这里遇到了TypeScript类型推断的坑,咱们一步步来拆解原因和解决办法:
问题根源分析
首先看你的ObservableModel定义:它继承了Observable<any>,内部的source是Observable<any>类型。当你在模板里用prod.collection | async时,async管道订阅的是CollectionModel这个可观察对象的流,但因为ObservableModel的泛型是any,TypeScript无法推断出这个流发射的值的具体类型,所以就会抛出「Object is of type unknown」的错误。
而你直接访问prod.collection.loaded能工作,是因为你在同步访问当前实例的属性——prod.collection本身就是CollectionModel的实例,你直接拿它的entityState.loaded值,并没有经过async管道订阅它的可观察流。但这里有个隐藏问题:这种方式不是响应式的,如果collection内部调用__internalSource.next(this)更新了状态,你的组件不会自动刷新,除非product本身也发射了新的实例。
解决办法:给可观察类添加泛型约束
要让TypeScript正确推断嵌套可观察对象的类型,核心是给ObservableModel和EntityModel加上泛型,明确每个子类的具体类型:
1. 修改ObservableModel的泛型定义
把原来的Observable<any>改成带泛型的形式,让它能关联到具体的子类:
export class ObservableModel<T extends ObservableModel<T>> extends Observable<T> { private internalSource = new BehaviorSubject<T>(this as T); source = this.internalSource.asObservable(); constructor() { // 把内部source的订阅传递给父类Observable super(subscriber => this.source.subscribe(subscriber)); } }
这里用泛型T指代当前子类,internalSource的类型就变成了BehaviorSubject<T>,source也对应成Observable<T>,TypeScript就能准确推断流的输出类型了。
2. 给EntityModel添加泛型继承
/** * Base entity class which all models should extend */ export class EntityModel<T extends EntityModel<T>> extends ObservableModel<T> { protected entityData: AnyEntity; protected entityState: { loading: boolean; loaded: boolean; reloading: boolean; error: boolean; } = { loading: false, loaded: false, reloading: false, error: false, }; constructor(public type: string, public id?: string) { super(); } from(value: AnyEntity) { const { type, id } = value; this.entityState = { loaded: true, loading: false, reloading: false, error: false, }; this.entityData = value; this.internalSource.next(this as T); } load() { ... } reload() { ... } private requestData() { ... } }
注意要调用super()来初始化父类的构造函数。
3. 子类继承时指定自身类型
让CollectionModel和ProductModel继承时明确泛型参数为自身:
export class CollectionModel extends EntityModel<CollectionModel> { @attr name: string; } export class ProductModel extends EntityModel<ProductModel> { @attr name: string; @attr description: string; @hasOne('collection') collection: CollectionModel; }
4. 模板中的使用验证
现在你再用prod.collection | async时,TypeScript就能正确推断出返回的是CollectionModel类型,不会再报错了:
<div *ngIf="product | async as prod"> <div *ngIf="prod.collection | async as collection"> {{collection.loaded}} <!-- 现在TS能识别collection是CollectionModel类型,不会报错 --> </div> </div>
额外优化提示
- 把原来的
__internalSource改成TypeScript规范的私有属性(用private关键字),避免外部代码直接修改内部的Subject。 - 确保
ObservableModel的构造函数正确继承了Observable的逻辑,这样你直接订阅ObservableModel实例(比如product | async)时,才能正确接收到内部source的数据流。
备注:内容来源于stack exchange,提问作者transformerTroy

