我正在努力为函数参数添加正确的类型,其中函数应该是通用的,并且参数应该相互关联。
这是一个最小的可重现示例(在实际代码中,类型
Foo
和对象objectOne
来自我无法更改的库,而函数func
是我试图自己创建的函数):
interface Foo {
union: 'one' | 'two' | 'three';
object: {
One: {'oneRelatedKey': 'oneRelatedValue'};
Two: {'twoRelatedKey': 'twoRelatedValue'};
Three: {'threeRelatedKey': 'threeRelatedValue'};
};
};
const objectOne = {
one: {
func: (obj: Foo['object']['One']) => {console.log(obj)}
},
two: {
func: (obj: Foo['object']['Two']) => {console.log(obj)}
},
three: {
func: (obj: Foo['object']['Three']) => {console.log(obj)}
},
};
const func = <T extends Foo['union']>(one: T, two: Foo['object'][Capitalize<T>]) => {
objectOne[one].func(two);
}
我在
two
: 上遇到错误
Property ''twoRelatedKey'' is missing in type '{ oneRelatedKey: "oneRelatedValue"; }' but required in type '{ twoRelatedKey: "twoRelatedValue"; }'
我想确保使用
func
中的字符串和 Foo['union']
中的相应对象调用 Foo['object']
,并在传入 Foo['union']
参数的大写版本上进行索引。
然后应该可以按如下方式调用
func
:
func('one', { 'oneRelatedKey': 'foo' })
我尝试了以下方法,看起来更接近:
type FirstTestGeneric<T extends Foo['union']> = T;
type FirstTest = FirstTestGeneric<'one'>; // 'one'
type SecondTestGeneric<T extends Foo['union']> = Capitalize<T> extends keyof Foo['object'] ? Foo['object'][Capitalize<T>] : never;
type SecondTest = SecondTestGeneric<'one'>; // {'oneRelatedKey': string}
const testFunc = <T>(one: FirstTestGeneric<'one'>, two: SecondTestGeneric<'one'>) => {
objectOne[one].func(two);
}
现在我只需要以某种方式修改
testFunc
以便它使用类型参数。由于某种原因,这不起作用:
const testFunc = <T extends Foo['union']>(one: FirstTestGeneric<T>, two: SecondTestGeneric<T>) => {
objectOne[one].func(two);
}
我认为问题在于类型
T
接受联合。如果它只接受一个字符串(来自联合Foo['union']
),它可能会起作用。
通过一点点类型操作就可以实现:
interface Foo {
union: 'one' | 'two' | 'three';
object: {
One: {'oneRelatedKey': 'oneRelatedValue'};
Two: {'twoRelatedKey': 'twoRelatedValue'};
Three: {'threeRelatedKey': 'threeRelatedValue'};
};
}
type Temp = Record<Foo['union'], any>
type OneObjType = {
[K in keyof Temp]: { func: (x: Foo['object'][Capitalize<K>]) => void }
}
const objectOne = {
one: {
func: (obj: Foo['object']['One']) => {console.log(obj)}
},
two: {
func: (obj: Foo['object']['Two']) => {console.log(obj)}
},
three: {
func: (obj: Foo['object']['Three']) => {console.log(obj)}
},
};
const retyped: OneObjType = objectOne; // NOTE: *NOT* unsafe
const func = <T extends Foo['union']>(one: T, two: Foo['object'][Capitalize<T>]) => {
retyped[one].func(two);
}
注意,你原来的函数是正确的,我没有修改它。诀窍是让编译器接受
objectOne
中方法的参数映射回联合,并且联合连接到 Foo
接口中的相应对象。另请注意,这是对类型的重新解释,而不是强制转换:编译器仍然确保安全。