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

如何从只读数组构造拼接字符串类型?

如何从只读数组构造拼接字符串类型?

我懂你现在的困扰——你想基于只读字符串数组的类型,生成类似"foo" | "bar"这种格式的拼接字符串字面量类型,但自己写的递归类型没跑通,反而返回了空字符串。咱们来一步步把这个问题解决掉。

先把你的代码贴出来方便分析:

const foos = ['foo', 'bar'] as const;
type Foos = typeof foos;
type Foo = Foos[number];
const concatFoos = `"foo" | "bar"`;

// 你尝试的类型,但返回空
export type UnionType<T extends readonly F[], F extends string> = 
  T extends [infer U extends F, ...infer V extends any[]] 
    ? V['length'] extends 0 
      ? `"${U}"` 
      : `"${U}" | "${UnionType<V, F>}"` 
    : '';

问题出在哪?

你写的递归逻辑思路是对的,但有个关键细节没注意到:你的原数组Foos是只读数组类型(readonly ["foo", "bar"]),但递归时你把剩余元素V约束成了普通的any[],这就导致递归分支里的V匹配不到只读数组的结构,最后直接走到了''的默认分支,所以才会得到空字符串。

修正方案

只需要把递归时V的约束改成和原数组一致的readonly F[],让类型系统能正确识别剩余元素的只读数组结构就行:

export type UnionType<T extends readonly F[], F extends string> = 
  T extends [infer U extends F, ...infer V extends readonly F[]] 
    ? V['length'] extends 0 
      ? `"${U}"` 
      : `"${U}" | "${UnionType<V, F>}"` 
    : '';

现在测试一下:

type Result = UnionType<Foos, Foo>;
// Result 会被推断为 '"foo" | "bar"',完美匹配 concatFoos 的类型

还可以再优化一下

其实第二个类型参数F是多余的,我们可以直接从只读数组T里提取元素类型,让类型定义更简洁:

export type UnionType<T extends readonly string[]> = 
  T extends [infer U extends string, ...infer V extends readonly string[]] 
    ? V['length'] extends 0 
      ? `"${U}"` 
      : `"${U}" | "${UnionType<V>}"` 
    : '';

用的时候直接传只读数组的类型就行:

type Result = UnionType<Foos>;
// 同样得到 '"foo" | "bar"'

原理简单说下

这个递归类型的工作逻辑很直观:

  • 先判断当前数组T是不是非空的只读字符串数组,解构出第一个元素U和剩余元素组成的数组V
  • 如果剩余数组V是空的(长度为0),就直接返回带双引号的当前元素字符串
  • 如果还有剩余元素,就拼接当前元素的带引号字符串、|,再递归处理剩余数组
  • 如果传入的是空数组,就返回空字符串(这个分支可以根据你的需求改成never或者其他默认值)

比如你用更长的数组测试:

const moreFoos = ['a', 'b', 'c'] as const;
type LongResult = UnionType<typeof moreFoos>;
// LongResult 会是 '"a" | "b" | "c"'

内容来源于stack exchange

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.08 11:53:09