我期待加入一个未知/无限数量的数字字符串(与/不与px).使用此代码,您只能连接两个项目.那么,有没有一种方法可以使用一个空格连接多个项目?如果没有,有没有一种方法可以很容易地对多达12个项目(最好是递归的,而不是在添加另一个项目的情况下加入12个|)?

Typescript 版本:4.9.5

export type Format = `${number}` | `${number}px`;

// Somehow create a join of `Format` separated by a space with unlimited number of values
export type Input = `${Format} `${Format}`;

我希望将所有这些(以及更多)验证为有效字符串:

const input: Input = '1';
const input: Input = '1 2';
const input: Input = '1 2 3';
const input: Input = '1 2 10px';
const input: Input = '10px 1 2 20px';
// And so on

推荐答案

你可以使用递归条件类型来实现这一点:

type Last<T extends unknown[]> =
    T extends [...unknown[], infer U]
        ? U
        : never

type JoinedUnion<
    Format extends string,
    Limit extends number,
    Acc extends string[] = [Format],
> =
    Last<Acc> extends string
        ? Acc['length'] extends Limit
            ? Acc[number]
            : JoinedUnion<
                Format,
                Limit,
                [...Acc, `${Last<Acc>} ${Format}`]
            >
        : never

首先使用Last来获取元组的最后一个索引的类型,这将在后面用到.

那么这款JoinedUnion型就可以:

  • Format作 for each 术语的字符串文字
  • Limit允许的最大递归级别.这会告诉Typescript 什么时候该退出.
  • Acc是到目前为止累积的字符串文字类型的元组,用于在内部收集结果并知道何时停止.

逐行:

  • Last<Acc> extends string解决了一个类型错误,我不完全确定为什么它是一个错误.但这向类型判断器证明了元组中的最后一项是字符串类型.
  • ? Acc['length'] extends Limit这会判断我们是否达到了极限.
  • ? Acc[number]已达到限制,因此将构建元组索引为number,以获得所有值的联合.

然后我们开始递归:

: JoinedUnion<
  Format,
  Limit,
  [...Acc, `${Last<Acc>} ${Format}`]
>

如果我们还没有达到预期的长度,我们就会到达这里.因此,我们使用相同的格式和限制再次调用JoinedUnion类型,但将一个条目附加到我们正在构建的元组中.该元组是我们之前构建的所有内容(...Acc),然后是一个字符串文字类型的新项,其中元组中的最后一项加上一个空格和所需的格式.这一部分是构建下一层的基础.

让我们来测试一下:

type TestAB = JoinedUnion<'a' | 'b', 3>
"a" | "b" | "a a" | "a b" | "b a" | "b b" | "a a a" | "a a b" | "a b a" | "a b b" | "b a a" | "b a b" | "b b a" | "b b b"

我认为这就是你想要的.


对于您的示例值,它也同样适用:

export type Format = `${number}` | `${number}px`;

export type Input = JoinedUnion<Format, 12>

const inputA: Input = '1';
const inputB: Input = '1 2';
const inputC: Input = '1 2 3';
const inputD: Input = '1 2 10px';
const inputE: Input = '10px 1 2 20px';

const inputBad: Input = '10px 1 2 20pxz'; // error

See playground


注意:这将解析为拥有超过8000个成员的联盟类型.如果您采用长度16,这使您的成员长度约为131,000,那么在抛出以下代码之前,这似乎是最大打字脚本所允许的:

Expression produces a union type that is too complex to represent.(2590)

这可能会使类型判断变慢.

这些权衡是否适合您的用例取决于您.

Typescript相关问答推荐

类型脚本不会推断键的值类型

React Hook中基于动态收件箱定义和断言回报类型

防止获取发出不需要的请求

是否可以根据嵌套属性来更改接口中属性的类型?

在Typescribe中,extends工作,但当指定派生给super时出错""

如果存在ts—mock—imports,我们是否需要typescpt中的IoC容器

在不更改类型签名的情况下在数组并集上映射文字

Select 下拉菜单的占位符不起作用

有什么方法可以类型安全地包装import()函数吗?

为什么在leetcode问题的测试用例中,即使我确实得到了比预期更好的解决方案,这段代码也失败了?

在Mac和Windows上运行的Web应用程序出现这种对齐差异的原因是什么?(ReactNative)

如何在使用条件类型时使用void

S,为什么我的T扩展未定义的条件在属性的上下文中不起作用?

基于未定义属性的条件属性

在Thunk中间件中输入额外参数时Redux工具包的打字脚本错误

可以将JS文件放在tsconfig';s includes/files属性?或者,我如何让tsc判断TS项目中的JS文件?

定义一个只允许来自另一个类型的特定字符串文字的类型的最佳方式

如何使用 Angular 确定给定值是否与应用程序中特定单选按钮的值匹配?

在 next.js 13.4 中为react-email-editor使用forwardRef和next/dynamic的正确方法

为什么我们在条件语句中使用方括号[]?