【问题标题】:TypeScript - class dictionary generic that holds other generic types - problem with generic types being lost inside methodTypeScript - 包含其他泛型类型的类字典泛型 - 泛型类型在方法中丢失的问题
【发布时间】:2021-07-13 11:21:02
【问题描述】:

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

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

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

您怎么看,这是一个错误,还是有正当理由以它的工作方式工作?如果是后者,有没有办法修改自定义类型保护isAlpha,保留泛型类型?

Playground Link

// 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'); 

【问题讨论】:

    标签: typescript typescript-typings typescript-generics


    【解决方案1】:

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

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

    即使在最简单的情况下,将值返回为条件返回类型也是一个问题。我们知道valuetrue,它并不总是 意味着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

    所以是的,您只需使用return value as ValueFromEntry&lt;Entries[EntryId]&gt; 断言正确性。您已经知道该怎么做,但希望您能更好地理解为什么需要它。

    【讨论】:

      猜你喜欢
      • 2019-05-30
      • 1970-01-01
      • 2022-10-14
      • 1970-01-01
      • 1970-01-01
      • 2021-02-16
      • 2019-10-06
      • 2019-05-26
      • 1970-01-01
      相关资源
      最近更新 更多