TypeScript-包含其他泛型类型的类字典泛型-泛型类型在方法内部丢失的问题



我有一个半复杂的例子,其中有一个类Store,它有一个其他";值类";(Alpha<AlphaVal>Beta<BetaVal>(作为其通用参数并且它具有从该字典返回值(由"值类"持有的值(的get方法。

我可以为get的返回值创建类型,并且它可以正常工作。然而,我对get内部发生的事情有一个问题。

由于某种原因,这些值类的泛型丢失了。当使用自定义类型谓词来缩小类型时,它尤其会丢失,但我认为问题不仅在于自定义类型保护,还在于get方法中entry的并集类型已经丢失了泛型。

你怎么看,这是一个bug,还是有正当理由让它以这种方式工作?如果是后者,是否有方法修改自定义类型保护isAlpha以保留泛型类型?

游乐场链接

// Base types for value classes
type AlphaVal = { [index: string]: any}; 
type BetaVal = number | string | boolean;
// Value class - holds a value
class Alpha<T extends AlphaVal> {
private value: T;
public isAlpha() {  }
public get(): T { return this.value; }
}
class Beta<T extends BetaVal> {
private value: T;
public isBeta() { };
public get(): T { return this.value }
}
// type AsAlpha<T> = Extract<T, Alpha<AlphaVal>>;
type AsAlpha<T> = T extends Alpha<infer R>
? Extract<T, Alpha<R>> : Extract<T, Alpha<AlphaVal>>;
// The type guard
const isAlpha = <V extends Alpha<AlphaVal> | Beta<BetaVal>>(
value: V
): value is AsAlpha<V> => {
return (value instanceof Alpha);
}
// Converts Alpha<R> to R, Beta<R> to R
type ValueFromEntry<T extends Alpha<AlphaVal> | Beta<BetaVal>> =
T extends Alpha<infer R>
? R : T extends Beta<infer R>
? R : unknown;
class Store<Entries extends { [index: string]: Alpha<AlphaVal> | Beta<BetaVal> }> {
private entries = { } as Entries;
// Gets entry value
public get<EntryId extends keyof Entries>(
entryId: EntryId
): ValueFromEntry<Entries[EntryId]> { // return type works properly
//
let entry = this.entries[entryId];
if (isAlpha(entry)) {
entry.isAlpha(); // just ensuring that the type guard works
let value = entry.get();
// The problem here is that the type is Alpha<AlphaValue>
// and the generic is lost, so it's not assignable to the return value
return value;
// of course something like below would work, but I'm trying to
// find out if there's a better way.
// return value as ValueFromAlphaBeta<Entries[EntryId];
}
// However, it may not be just an issue with the type guard, but with
// how the entry type is typed inside the function.
// If you hover on entry.get() below, it's set to return AlphaVal | BetaVal,
// it appears the generic is lost even without type narrowing.
return entry.get();
// The interesting thing is that it works properly on the return type.
}
}
type SampleStore = {
a: Alpha<{ test: true }>
b: Beta<5>
}
const sampleStore = new Store<SampleStore>();
// types on return value work properly, generic is preserved
let value = sampleStore.get('a'); 

您的代码很复杂,但问题的核心可以归结为非常简单的事情。选中if (isAlpha(entry))时,会缩小变量entry的类型。它不会缩小泛型类型参数EntryId的类型。

您无法知道条目类型是AlphaVal,而仅是AlphaVal,因为类型EntryId可能是更广泛的类型,如'alpha1' | 'alpha2' | 'beta1',甚至只是keyof Entries

因此,即使在最简单的情况下,将值返回到条件返回类型也是一个问题。我们知道value就是true,它并不总是意味着T extends true

const test = <T extends boolean>(value: T): T extends true ? "A" : "B" => {
if ( value ) {
return "A"; // error: Type '"A"' is not assignable to type 'T extends true ? "A" : "B"'
} else {
return "B"; // error: Type '"B"' is not assignable to type 'T extends true ? "A" : "B"'
}
}

打字游戏场链接

所以,是的,你必须用return value as ValueFromEntry<Entries[EntryId]>断言正确性。你已经知道该怎么做了,但希望你能更好地理解为什么需要它。

最新更新