像这样的课程
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]
的约束?
答案 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>
因此,这可行,并且避免了早期方法的问题,但是可能会增加一些自身的问题。 ♀️
好的,希望其中之一可以帮助您取得进步。祝你好运!