如何保证使用 Typescript 可以调用传递的对象键?
How to guarantee that passed object key is callable using Typescript?
我正在尝试为特定用例创建 React 高阶组件,问题归结为以下几点:
function sample<TObj, P extends keyof TObj, F extends keyof TObj>(
obj: TObj,
prop: P,
setProp: TObj[F] extends (value: TObj[P]) => void ? F : never
) {
obj[setProp](obj[prop]);
}
我希望能够传递一个对象,一个应该是该对象的键的字符串,以及该对象的另一个键但必须是一个函数。
这可以进一步简化为:
function sample2<TObj, F extends keyof TObj>(
obj: TObj,
setProp: TObj[F] extends () => void ? F : never
) {
obj[setProp]();
}
在我看来,因为我使用了条件类型,所以可以保证 obj[setProp]
将是一个函数但是我得到错误:
This expression is not callable.
Type 'unknown' has no call signatures.ts(2349)
如下所示,如果使用不符合要求的密钥调用该函数,该函数将出错。但同样的要求似乎并没有在函数内部应用。
我知道这可以看作是一个 XY 问题,但它让我真正感兴趣的是是否有办法使这个特定问题正常工作。
在 sample2()
的实现中,类型 TObj[F] extends () => void ? F : never
是一个未解析的条件类型。也就是说,它是一种条件类型,取决于要解析的当前未指定的泛型类型参数。在这种情况下,编译器通常不知道如何处理它并将其视为本质上不透明。 (有关此的一些讨论,请参阅 microsoft/TypeScript#23132。)特别是它没有意识到 TObj[Tobj[F] extends ()=>void ? F : never]
最终必须解析为 ()=>void
.
的某些子类型
通常我会完全避免条件类型,除非它们是必要的。编译器可以更容易地从 mapped types 中理解和推断像 Record<K, V>
:
function sample2<K extends PropertyKey, T extends Record<K, () => void>>(
obj: T,
prop: K
) {
obj[prop]();
}
当你调用它时,它的行为类似:
const obj2 = {
func() { console.log("func") },
prop: 42
};
sample2(obj2, "func"); // okay,
//sample2(obj, "prop"); // error
// ~~~ <-- number is not assignable to ()=>void
编辑:为了解决原来的 sample()
,我会使用这个定义:
function sample<
PK extends PropertyKey,
FK extends PropertyKey,
T extends Record<PK, any> & Record<FK, (v: T[PK]) => void>
>(
comp: T,
prop: PK,
setProp: FK
) {
comp[setProp](comp[prop]);
}
const obj = {
func(z: number) { console.log("called with " + z) },
prop: 42
}
我认为这也符合您的要求:
sample(obj, "prop", "func"); // called with 42
sample(obj, "prop", "prop"); // error!
// ~~~ <-- number not assignable to (v: number)=>void
sample(obj, "func", "func"); // error!
// ~~~ <-- (v: number)=>void not assignable to number
好的,希望对你有帮助;祝你好运!
我正在尝试为特定用例创建 React 高阶组件,问题归结为以下几点:
function sample<TObj, P extends keyof TObj, F extends keyof TObj>(
obj: TObj,
prop: P,
setProp: TObj[F] extends (value: TObj[P]) => void ? F : never
) {
obj[setProp](obj[prop]);
}
我希望能够传递一个对象,一个应该是该对象的键的字符串,以及该对象的另一个键但必须是一个函数。
这可以进一步简化为:
function sample2<TObj, F extends keyof TObj>(
obj: TObj,
setProp: TObj[F] extends () => void ? F : never
) {
obj[setProp]();
}
在我看来,因为我使用了条件类型,所以可以保证 obj[setProp]
将是一个函数但是我得到错误:
This expression is not callable.
Type 'unknown' has no call signatures.ts(2349)
如下所示,如果使用不符合要求的密钥调用该函数,该函数将出错。但同样的要求似乎并没有在函数内部应用。
我知道这可以看作是一个 XY 问题,但它让我真正感兴趣的是是否有办法使这个特定问题正常工作。
在 sample2()
的实现中,类型 TObj[F] extends () => void ? F : never
是一个未解析的条件类型。也就是说,它是一种条件类型,取决于要解析的当前未指定的泛型类型参数。在这种情况下,编译器通常不知道如何处理它并将其视为本质上不透明。 (有关此的一些讨论,请参阅 microsoft/TypeScript#23132。)特别是它没有意识到 TObj[Tobj[F] extends ()=>void ? F : never]
最终必须解析为 ()=>void
.
通常我会完全避免条件类型,除非它们是必要的。编译器可以更容易地从 mapped types 中理解和推断像 Record<K, V>
:
function sample2<K extends PropertyKey, T extends Record<K, () => void>>(
obj: T,
prop: K
) {
obj[prop]();
}
当你调用它时,它的行为类似:
const obj2 = {
func() { console.log("func") },
prop: 42
};
sample2(obj2, "func"); // okay,
//sample2(obj, "prop"); // error
// ~~~ <-- number is not assignable to ()=>void
编辑:为了解决原来的 sample()
,我会使用这个定义:
function sample<
PK extends PropertyKey,
FK extends PropertyKey,
T extends Record<PK, any> & Record<FK, (v: T[PK]) => void>
>(
comp: T,
prop: PK,
setProp: FK
) {
comp[setProp](comp[prop]);
}
const obj = {
func(z: number) { console.log("called with " + z) },
prop: 42
}
我认为这也符合您的要求:
sample(obj, "prop", "func"); // called with 42
sample(obj, "prop", "prop"); // error!
// ~~~ <-- number not assignable to (v: number)=>void
sample(obj, "func", "func"); // error!
// ~~~ <-- (v: number)=>void not assignable to number
好的,希望对你有帮助;祝你好运!