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

美化联合类型时出现类型实例化过深或无限递归错误

TypeScript递归联合类型加Prettify后赋值报错的解决办法

我想让API使用者看到ACTION_1 | ACTION_2 | etc这种直观的联合类型输出,就写了这俩工具类型:

export type Prettify<T> = T extends infer R ? { [K in keyof R]: R[K] } : never;

export type ActionUnion<T> = Prettify<{
  [K in keyof T]: K extends `actions`
    ? keyof T[K] | ActionUnion<T[K]>
    : T[K] extends Record<string, unknown>
    ? ActionUnion<T[K]>
    : never;
}[keyof T]>;

但把ActionUnion<T>用作machine函数里transition方法的参数类型时,赋值action给actionStr就报错了,去掉Prettify包着就没事:

type Config = {
  actions?: Record<string, string>;
  states?: Record<string, Config>;
}

export function machine<T extends Config>(config: T) {
  return {
    transition,
  };

  function transition(action: ActionUnion<T>) {
    const actionStr = action; // <-- 报错:ActionUnion<T> 不能赋值给 string
  }
}

注意:这个问题的递归逻辑和之前StackOverflow上的类似问题不一样,那些方案不管用。

问题原因

原来的Prettify会把所有类型都转成对象结构展开,哪怕是字符串联合类型也会被当成对象处理,导致TypeScript识别不出它其实是string的子集。递归生成的字符串字面量联合经过这层包装后,类型推断就乱了,自然没法赋值给string变量。

解决方案

改一下Prettify,只对对象类型做展开美化,非对象类型直接原封不动返回:

// 只处理对象类型,非对象直接返回原类型
export type Prettify<T> = T extends object 
  ? T extends infer R ? { [K in keyof R]: R[K] } : never
  : T;

export type ActionUnion<T> = Prettify<{
  [K in keyof T]: K extends `actions`
    ? keyof T[K] | ActionUnion<T[K]>
    : T[K] extends Record<string, unknown>
    ? ActionUnion<T[K]>
    : never;
}[keyof T]>;

这样一来,当ActionUnion<T>算出的是字符串联合时,Prettify直接把这个联合返回,TypeScript能正确识别它属于string类型,赋值就不会报错了,同时对象类型的美化效果也保留了。

内容的提问来源于stack exchange,提问作者Andy Jessop

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 14:42:14