我有一个通用类,该类接受其他类型和该类型的键名。我还想用此键名初始化属性。我已经在TS中定义了此名称,但是我必须将键的名称显式传递为通用参数,并将相同的值传递给构造函数。看起来有点不必要-这些值始终相同(类型和值)。
是否可以从其构造函数参数推断通用类的类型?或使用通用的“类型”作为“值”(可能不是-我遇到了错误:'ValueKey' only refers to a type, but is being used as a value here.ts(2693)
)。
代码定义:
interface InterfaceWithProperties {
stringProperty: string;
numberProperty: number;
}
class GenericCLass<TypeWithKeys, ExactKey extends keyof TypeWithKeys> {
keyNameFromAnotherType: ExactKey; // this property should have value equal to key from another type;
// is it a way to remove this parameter? it's exactly duplicated with ExactKey
// or to detect correctly ExactKey type based on `property` value
constructor(keyNameFromAnotherType: ExactKey) {
// this.property = ExactKey; // ofc: not working code
this.keyNameFromAnotherType = keyNameFromAnotherType;
}
}
当前用法:
const test1 = new GenericCLass<InterfaceWithProperties, 'stringProperty'>('stringProperty');
我想要相同的结果,但是没有这个额外的参数。像这样:
const test2 = new GenericCLass<InterfaceWithProperties, 'stringProperty'>();
或这样的东西:
const test3 = new GenericCLass<InterfaceWithProperties>('stringProperty');
TS游乐场,其代码为:link
答案 0 :(得分:2)
问题是您要手动指定一个类型参数,并让编译器推断另一个参数。这称为partial type argument inference,而TypeScript没有它(从TS3.4开始)。您既可以手动指定所有类型参数(不想执行),也可以让所有类型参数由编译器推断(您不能这样做,因为您无法从中推断出指定类型)。>
有两种主要的解决方法:
首先是完全依靠类型推断,并使用 dummy参数来推断通常要指定的类型。例如:
class GenericCLass<T, K extends keyof T> {
keyNameFromAnotherType: K;
// add dummy parameter
constructor(dummy: T, keyNameFromAnotherType: K) {
this.keyNameFromAnotherType = keyNameFromAnotherType;
}
}
const test = new GenericCLass(null! as InterfaceWithProperties, 'stringProperty');
// inferred as GenericCLass<InterfaceWithProperties, "stringProperty">
您可以看到作为第一个参数传入的值在运行时只是null
,而构造函数无论如何都不会在运行时查看它。但是已经告知类型系统为InterfaceWithProperties
类型,足以按照您想要的方式推断类型。
另一种解决方法是通过currying将通常使用部分推理的所有内容分解为两部分;第一个泛型函数将允许您指定一个参数,并返回一个推断另一个参数的泛型函数(在本例中为泛型构造函数)。例如
// unchanged
class GenericCLass<T, K extends keyof T> {
keyNameFromAnotherType: K;
constructor(keyNameFromAnotherType: K) {
this.keyNameFromAnotherType = keyNameFromAnotherType;
}
}
// curried helper function
const genericClassMaker =
<T>(): (new <K extends keyof T>(
keyNameFromAnotherType: K
) => GenericCLass<T, K>) =>
GenericCLass;
// specify the one param
const InterfaceWithPropertiesGenericClass =
genericClassMaker<InterfaceWithProperties>();
// infer the other param
const test = new InterfaceWithPropertiesGenericClass('stringProperty');
// inferred as GenericCLass<InterfaceWithProperties, "stringProperty">
这将单独保留类定义,但会创建一个新的帮助器函数,该函数将返回GenericClass
构造函数的部分指定版本供您使用。您可以一次完成,但是很丑:
const test = new (genericClassMaker<InterfaceWithProperties>())('stringProperty');
// inferred as GenericCLass<InterfaceWithProperties, "stringProperty">
无论如何,希望其中之一对您有用。祝你好运!
答案 1 :(得分:2)
坏的新消息是您不能轻易做到这一点。最好的方法是让编译器根据构造函数args推断ExactKey
。仅当我们让Typescript推断所有类型参数时,才可以这样做。但是您需要指定一个类型参数并进行推断,这是不可能的,因为不支持部分推断(至少从3.5开始,有计划添加它,但是将其推回,然后从中删除路线图)。
一种选择是使用带有静态函数的函数currying而不是构造函数:
interface InterfaceWithProperties {
stringProperty: string;
numberProperty: number;
}
class GenericCLass<TypeWithKeys, ExactKey extends keyof TypeWithKeys> {
keyNameFromAnotherType: ExactKey;
private constructor(keyNameFromAnotherType: ExactKey) {
this.keyNameFromAnotherType = keyNameFromAnotherType;
}
static new<T>(){
return function <ExactKey extends keyof T>(keyNameFromAnotherType: ExactKey) {
return new GenericCLass<T, ExactKey>(keyNameFromAnotherType);
}
}
}
const test1 = GenericCLass.new<InterfaceWithProperties>()('stringProperty')
另一种选择是在构造函数args中为编译器提供TypeWithKeys
的推理站点:
interface InterfaceWithProperties {
stringProperty: string;
numberProperty: number;
}
class GenericCLass<TypeWithKeys, ExactKey extends keyof TypeWithKeys> {
keyNameFromAnotherType: ExactKey;
constructor(t: TypeWithKeys, keyNameFromAnotherType: ExactKey) {
this.keyNameFromAnotherType = keyNameFromAnotherType;
}
}
const test1 = new GenericCLass(null as InterfaceWithProperties, 'stringProperty')
或者如果null as InterfaceWithProperties
吓到您,您可以使用Type
类:
class Type<T> { private t:T }
class GenericCLass<TypeWithKeys, ExactKey extends keyof TypeWithKeys> {
keyNameFromAnotherType: ExactKey;
constructor(t: Type<TypeWithKeys>, keyNameFromAnotherType: ExactKey) {
this.keyNameFromAnotherType = keyNameFromAnotherType;
}
}
const test1 = new GenericCLass(new Type<InterfaceWithProperties>(), 'stringProperty')
这两个解决方案都不是很好,也不是特别直观,partial argument inference 将是最佳解决方案。