有没有办法让这些泛型函数在 typescript 中工作?

11dmarpk  于 2023-01-10  发布在  TypeScript
关注(0)|答案(1)|浏览(152)

有没有办法使用泛型来正确地输入setValuegetValue函数的类型?我理解这个错误,因为它看到了给定属性的多种类型。但是有没有办法也让函数根据提供的属性值来识别name/age属性类型?

import { BehaviorSubject, Observable } from 'rxjs';

interface Person {
    age: BehaviorSubject<number>;
    name: BehaviorSubject<string>;
}

class SomeClass {
    private _somePerson:Person = {
        age: new BehaviorSubject(0),
        name: new BehaviorSubject('John')
    };

    public setValue<T>(inID: number, inProperty: keyof(Person), inValue: T): void {
        this._somePerson[inProperty].next(inValue);
    }

    public getValue<T>(inID: number, inProperty: keyof(Person)): Observable<T> {
        return this._somePerson[inProperty].asObservable();
    }

}

TSPlayground链接

wi3ka0sx

wi3ka0sx1#

从概念上讲,您希望将操作表示为indexing,其中genericK(对应于inProperty)转换为某个"基" Person类型,其中值仅为stringnumber,而不是BehaviorSubject<string>BehaviorSubject<number>

type BasePerson = {
    [K in keyof Person]: Person[K] extends BehaviorSubject<infer V> ? V : never
}

使用条件类型推理将BehaviorSubject<V>转换为V,等效于:

/* type BasePerson = {
  age: number;
  name: string;
}*/

然后这个类看起来像这样:

class SomeClass {
    private _somePerson: Person = {
        age: new BehaviorSubject(0),
        name: new BehaviorSubject('John')
    };

    public setValue<K extends keyof Person>(
        inID: number, inProperty: K, inValue: BasePerson[K]
    ): void {
        this._somePerson[inProperty].next(inValue); // error!
    }

    public getValue<K extends keyof Person>(
        inID: number, inProperty: K
    ): Observable<BasePerson[K]> {
        return this._somePerson[inProperty].asObservable(); // error!
    }
}

所有这些都适用于该类的消费者:

const sc = new SomeClass();
sc.setValue(123, "age", "oops"); // error
sc.setValue(123, "age", 123); // okay
sc.setValue(123, "name", 123); // error
sc.setValue(123, "name", "okay"); // okay
const obsNum = sc.getValue(123, "age"); 
// const obsNum: Observable<number>
const obsStr = sc.getValue(123, "name");
// const obsStr: Observable<string>

但是,正如您所看到的,编译器无法遵循setValue()getValue()实现内部的逻辑。对于 * generic * K,它需要查看BehaviorSubject<Person[K] extends BehaviorSubject<infer V> ? V : never>Person[K]的类型相同。不幸的是,这超出了编译器的当前能力。它会丢失 * correlation * 的跟踪在一米十五纳一和一米十六纳一之间。
所以你会收到错误消息说你正在做的事情可能不安全,这本质上就是microsoft/TypeScript#30581中描述的问题(那个问题讨论的是相关联合类型而不是泛型类型,但它是相同的底层问题)。
如果你不关心编译器在这些实现中验证类型安全,你可以只使用类型Assert来抑制警告:

public setValue<K extends keyof Person>(
    inID: number, inProperty: K, inValue: BasePerson[K]
): void {
    this._somePerson[inProperty].next(inValue as never); // okay
}

public getValue<K extends keyof Person>(
    inID: number, inProperty: K
): Observable<BasePerson[K]> {
    return this._somePerson[inProperty].asObservable() as any; // okay
}

这是因为类型Assert将验证类型安全性的负担从编译器转移到了开发人员身上。
如果你关心编译器验证这些实现的类型安全性,那么你需要按照microsoft/TypeScript#47109中的描述进行重构,而不是定义Person并从中计算BasePerson,你可以直接定义BasePerson

interface BasePerson {
    age: number;
    name: string;
}

然后Person可以表示为mapped type的性质:

type Person = { [K in keyof BasePerson]:
    BehaviorSubject<BasePerson[K]>
}

这种表示非常重要,因为现在Person[K]上的操作将保持泛型,也就是说,编译器可以在索引到Map类型时遵循相关性:

class SomeClass {
    private _somePerson: Person = {
        age: new BehaviorSubject(0),
        name: new BehaviorSubject('John')
    };

    public setValue<K extends keyof BasePerson>(
        inID: number, inProperty: K, inValue: BasePerson[K]
    ): void {
        this._somePerson[inProperty].next(inValue); // okay
    }

    public getValue<K extends keyof BasePerson>(
        inID: number, inProperty: K
    ): Observable<BasePerson[K]> {
        return this._somePerson[inProperty].asObservable(); // okay
    }
}

编译器发现this._somePerson[inProperty]Person[K]类型,它的计算结果是BehaviorSubject<BasePerson[K]>,在此之后,其他一切都正常工作。
Playground代码链接

相关问题