我有一个函数,它接受一个回调对象和一个具有与回调对象的键匹配的
type
属性的输入对象。
该函数使用输入对象调用匹配的回调。
我想创建一个高阶函数,它接受回调对象并返回一个接受匹配输入对象的函数。
但是,当我调用返回的函数时,我收到 TypeScript 错误,该错误指出输入对象不可分配给 never 类型。
type Actor <K extends string, T> = (input: { type: K } & T) => void
type Actors <K extends string, T> = { [Key in K]: Actor<K, T> }
type TfromA<K extends string, A extends Actors<K, any>> =
A extends Actors<K, infer T> ? T : never
export function marion<
K extends string,
A extends Actors<K, TfromA<K, A>>,
> (
actors: A,
input: { type: K } & TfromA<K, A>
): void {
const actor = actors[input.type]
actor(input)
}
interface Alpha { type: 'alpha', count: number }
interface Beta { type: 'beta', label: string }
const alphaBeta = {
alpha: (input: Alpha) => console.log(input.count),
beta: (input: Beta) => console.log(input.label)
}
const alpha: Alpha = { type: 'alpha', count: 42 }
marion(alphaBeta, alpha) // No errors
function higher<
K extends string,
A extends Actors<K, TfromA<K, A>>,
> (
actors: A
): (input: TfromA<K, A> & { type: K }) => void {
return function (input: TfromA<K, A> & { type: K }): void {
marion(actors, input)
}
}
const lower = higher(alphaBeta)
lower(alpha)
// Argument of type 'Alpha' is not assignable to parameter of type 'never'.
// The intersection 'Alpha & Beta & { type: string; }' was reduced to 'never' because property 'type' has conflicting types in some constituents.ts(2345)
如何创建一个可重用的函数来处理任何匹配的回调和输入对象集,并可用于创建更高阶的函数?
问题中的代码存在一些问题。让我们看看一个有效的版本,它是如何工作的,以及它与问题中的版本有何不同:
type Input<K, V> = { type: K } & V
type Actor<K, V> = (input: Input<K, V>) => void
type Actors<T extends object> =
{ [K in keyof T]: Actor<K, T[K]> }
export function marion<T extends object, K extends keyof T>(
actors: Actors<T>, input: Input<K, T[K]>
): void {
const actor = actors[input.type]
actor(input)
}
function higher<T extends object>(
actors: Actors<T>
): <K extends keyof T>(input: Input<K, T[K]>) => void {
return input => {
marion(actors, input)
}
}
我发现将
Input<K, V>
的类型别名指定为 {type: K} & V
很方便,因为该类型会出现多次。 Actor<K, V>
类型基本相同,但 Actors
映射类型 仅在您要转换的对象类型 T
中才是 generic。键
K
处的每个属性都是 Actor<K, T[K]>
(其中 T[K]
是键 T
处 K
属性的 value,这就是我使用
V
作为类型参数的原因)。
然后,
marion()
在该对象类型T
和特定输入键K
中是通用的。当您调用 marion()
时,TypeScript 可以从 T
类型的 actors
参数推断出 Actors<T>
。您无需显式编写 TfromA
实用程序类型即可使其工作。 请注意,actors[input.type]
属于 Actors<T>[K]
类型,其计算结果为 Actor<K, T[K]>
,并且由于 input
属于 Input<K, T[K]>
类型,因此您可以调用 actors[input.type](input)
。
现在
higher()
函数很容易编写,因为它只是 marion()
的 curried版本。它接受类型为
actors
的参数 Actors<T>
,因此只需在 T
中通用即可。 然后它返回另一个泛型函数,该函数具有类型参数K
。
当您用示例调用它时,所有这些都有效:
marion(alphaBeta, alpha); // okay
marion(gammaDelta, gamma); // okay
const lower = higher(alphaBeta); // okay
lower(alpha) // okay
您的原始版本不起作用的原因:
Actors<K extends string, T>
类型没有帮助,因为每个{ [Key in K]: Actor<K, T> }
意味着每个属性都是Actor<K, T>
类型,即使K
结果是键的union,我们也不能确定提前 K
将会是什么。通过使其成为 T
的每个属性的映射类型,我们处于有利位置,在检查 K
参数之前不必担心 input
。
你的
TfromA<K, A>
对于TypeScript来说很难进行一般性的推理。它是作为条件类型实现的,因此在函数 marion
和 higher
中,TypeScript 不知道 TfromA<K, A>
实际上是什么。由于 TypeScript 本身知道如何从 T
推断 A
,因此我们无需担心这一点。
您的
higher()
函数在 K
和 A
中是通用的,但它不需要 K
类型的输入。相反,它只需要 A
类型的输入。虽然 A
constrained 到与 K
相关的类型,但 TypeScript 无法从约束推断类型参数(请参阅 microsoft/TypeScript#7234)。而且您甚至不想在那里推断 K
,因为在调用返回的函数之前您不会知道 K
。 所以 K
无法推断,退回到 string
,然后你得到 TfromA<string, typeof alphaBeta>
,它只是 never
,因为 Actors<K, T>
将所有属性都扔进一个包里。 这使得 higher()
返回 (input: never) => void
,这本质上是不可调用的。