首页 > 解决方案 > TypeScript - 包含其他泛型类型的类字典泛型 - 泛型类型在方法中丢失的问题

问题描述

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

我可以为返回值创建类型get并且它可以正常工作。但是,我对内部发生的事情有疑问get

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

你怎么看,它是一个错误还是有正当理由它以它的工作方式工作?如果是后者,有没有办法修改自定义类型保护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'); 

标签: typescripttypescript-typingstypescript-generics

解决方案


Your code is complex but the core of the issue can be boiled down to something really simple. When you check if (isAlpha(entry)), this narrows the type of the variable entry. It does not narrow the type of the generic type parameter EntryId.

You cannot know that the entry type is AlphaVal and only AlphaVal because the type EntryId could be a broader type like 'alpha1' | 'alpha2' | 'beta1' or even just keyof Entries.

Returning a value to a conditional return type is a problem for this reason even in the simplest of cases. We we know that value is true, it doesn't always mean that 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"'
    }
}

Typescript Playground Link

So yes you have to just assert correctness with return value as ValueFromEntry<Entries[EntryId]>. Which you already know how to do, but hopefully you have a better understanding of why it's needed.


推荐阅读