如何指定依赖于构造函数参数的类型变量

时间:2018-06-20 21:00:29

标签: typescript

像这样的课程

class Extractor<T, K extends keyof T> {
    constructor(private item: T, private key: K) { }

    extract(): T[K] {
        return this.item[this.key];
    }
}

使用/扩展是不自然的,因为类型参数K采用字符串文字类型。

我希望类型参数为<T, R>,其中R实际上是T[K]先前计算的对象。但是,TypeScript不允许R引用构造函数参数,并且key参数的类型表达式不允许引用参数本身。另外,构造函数不能具有泛型类型参数。

那么我在哪里可以指定R = T[typeof key]的约束?

1 个答案:

答案 0 :(得分:1)

我不确定您要达到的目标,因此我不知道哪种解决方案最适合您:


使用conditional types根据类型K和您想要的T来计算R是可能的,但这可能会带来更多的麻烦:

type ValueOf<T> = T[keyof T];
type KeyofMatching<T, R extends ValueOf<T>> = 
  ValueOf<{ [K in keyof T]: T[K] extends R ? K : never }>;

KeyofMatching类型别名采用类型T及其属性值类型之一R,并返回所有返回该类型的键K。因此T[KeyofMatching<T, R>]始终是R。不幸的是,TypeScript不够聪明,无法意识到这一点,因此,如果您拥有前一种类型的值并想将其作为后一种类型的值返回,则必须使用一个断言。这是执行此操作的功能:

function asR<T, R extends ValueOf<T>>(x: T[KeyofMatching<T, R>]): R {
  return x as any;
}

现在您可以定义班级了:

class Extractor<T, R extends T[keyof T]> {
  constructor(private item: T, private key: KeyofMatching<T, R>) { }
  extract(): R {
    return asR(this.item[this.key]); // note the asR()
  }
}

此功能尽其所能,但是当您创建新的Extractor时,编译器实际上将无法推断您期望的R的窄值:

const extractorNotSoGood = new Extractor({a: "you", b: true}, "b");
// inferred as Extractor<{a: string, b: boolean}, string | boolean>;

如果您想要最窄的R,则必须明确指定它:

const e = new Extractor<{a: string, b: boolean}, boolean>({a: "you", b: true}, "b");

所以,这可行,但是有一些缺点。


另一种攻击方法是放弃使用构造函数,而使用静态生成器方法。此外,由于TypeScript无法理解this.item[this.key]的类型为R,我们可以通过存储密钥而不是使用密钥的函数来回避它。像这样:

class Extractor<T, R extends T[keyof T]> {
  constructor(private item: T, private extractorFunction: (x: T) => R) { }
  extract(): R {
    return this.extractorFunction(this.item);
  }
  static make<T, K extends keyof T>(item: T, key: K): Extractor<T, T[K]> {
    return new Extractor(item, i => i[key]);
  }
}

如果您使用make而不是构造函数,那么您会得到想要的行为和推断,我认为:

const e = Extractor.make({ a: "you", b: true }, "b");
// e is an Extractor<{a: string, b: boolean}, boolean>

因此,这可行,并且避免了早期方法的问题,但是可能会增加一些自身的问题。 ‍♀️


好的,希望其中之一可以帮助您取得进步。祝你好运!