You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript高阶函数中基于参数选字典回调及类型错误解决

TypeScript高阶函数类型匹配问题解决

现有一个函数marion,它接收回调对象和带有type属性的输入对象(type与回调对象的键匹配),能调用对应回调并传入输入对象。但将其封装为高阶函数higher后,调用返回的函数时出现TypeScript错误:输入对象无法赋值给never类型,原因是不同type的输入类型交集冲突被简化为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) // 无错误

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)
// 错误:类型“Alpha”的参数不能赋给类型“never”的参数。
// 交集“Alpha & Beta & { type: string; }”被简化为“never”,因为某些组成部分的“type”属性类型冲突。ts(2345)

问题分析

原类型设计的核心问题在于Actors<K, T>强制所有回调共享同一个T,但实际场景中不同type对应的输入结构(如Alpha和Beta)是不同的。TfromA尝试从Actors中推断T,但会得到所有输入类型的交集,而Alpha和Beta的type属性分别为'alpha'和'beta',交集冲突导致推断结果为never,最终引发类型错误。

解决方案

需要重构类型定义,让每个回调对应各自的输入类型,而非统一的T:

  • 重新定义基础类型:

    • Actor<I>:直接接收具体的输入类型I,无需拆分K和T。
    • Actors<I>:回调对象的键对应输入类型的type属性值,每个键的回调接收对应的输入类型。
  • 提取合法输入联合类型:
    定义InputFromActors<A>,从回调对象A中提取所有合法的输入类型,形成联合类型。

  • 重构函数:
    调整marion和higher的泛型约束,使用新的类型定义,确保TypeScript能正确推断每个输入对应的回调。

修改后的完整代码

// 单个回调类型,接收具体的输入类型I
type Actor<I extends { type: string }> = (input: I) => void

// 回调对象类型,键为输入的type值,对应回调接收该type的输入
type Actors<I extends { type: string }> = {
  [K in I['type']]: Actor<Extract<I, { type: K }>>
}

// 从回调对象中提取所有合法的输入类型联合
type InputFromActors<A extends Actors<any>> = A extends Actors<infer I> ? I : never

export function marion<I extends { type: string }, A extends Actors<I>>(
  actors: A,
  input: I
): void {
  const actor = actors[input.type as keyof A]
  actor(input as Extract<I, { type: keyof A }>)
}

interface Alpha { type: 'alpha', count: number }
interface Beta { type: 'beta', label: string }
type AlphaBetaInput = Alpha | Beta

const alphaBeta: Actors<AlphaBetaInput> = {
  alpha: (input: Alpha) => console.log(input.count),
  beta: (input: Beta) => console.log(input.label)
}

const alpha: Alpha = { type: 'alpha', count: 42 }
const beta: Beta = { type: 'beta', label: 'test' }

marion(alphaBeta, alpha) // 正常工作
marion(alphaBeta, beta) // 正常工作

// 高阶函数定义
function higher<I extends { type: string }, A extends Actors<I>>(
  actors: A
): (input: InputFromActors<A>) => void {
  return (input: InputFromActors<A>) => {
    marion(actors, input)
  }
}

const lower = higher(alphaBeta)
lower(alpha) // 无错误
lower(beta) // 无错误

关键说明

  • Actor<I>直接绑定具体的输入类型,避免了拆分K和T带来的约束问题。
  • Actors<I>通过Extract<I, { type: K }>为每个type键匹配对应的输入类型,确保回调与输入一一对应。
  • InputFromActors<A>提取回调对象对应的所有输入联合类型,高阶函数返回的函数接收这个联合类型,TypeScript能自动进行类型窄化,确保输入与回调匹配。
  • 函数内部的类型断言是安全的,因为input.type必然是actors的键之一,对应的输入类型也必然匹配回调的参数类型。

内容的提问来源于stack exchange,提问作者David Y. Stephenson

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 09:43:14