假设我们有以下类型:

type DNA = {
    G: string;
    C: string;
    T: string;
    A: string;
}

type DNAKeys = keyof DNA;

是否有可能定义这样一种类型,即我们将由DNAKeys组成的字符串文字的每一种可能组合定义为平面字符串类型?

这个问题给了我很多语言语法定义的感觉,我主要是好奇这在目前的打字本上是否可能.

测试场景如下:

let dna: DNA = {
    G: 'C',
    C: 'G',
    T: 'A',
    A: 'U'
}

const doSomethingWithDNA = (dnaString: DNACombinations) => {
    for(const letter of dnaString){
        console.log(dna[letter]);
    }
}

我能想到的唯一可能的事情就是手动坐在那里,定义一个不断增加的组合词典,如下所示:

type DNACombinations = 
    | DNAKey
    | `${DNAKey}${DNAKey}`
    | `${DNAKey}${DNAKey}${DNAKey}`
    // ad infinitum

或者,就像实际的语法判断一样,这可能需要等到regex类型出现后才能执行?

推荐答案

如果没有microsoft/TypeScript#41160中讨论的真正的正则表达式验证类型,您try 做的事情是不可能的.不存在对应于仅由字符"A""C""G""T"组成的字符串的特定类型DNACombinations.

您目前所能得到的最接近的结果是使generic类型ValidDNACombinations<T>的行为类似于T上的constraint,因此T extends ValidDNACombinations<T>的充要条件是且仅当T可以分配给您想要的DNACombinations类型.这里有一种写这一点的方法:

type ValidDNACombinations<T extends string, A extends string = ""> =
    T extends `${infer F}${infer R}` ?
    F extends DNAKeys ? ValidDNACombinations<R, `${A}${F}`> : `${A}${DNAKeys}` :
    A;

这是一个tail-recursive conditional type,它使用template literal typesT解析为单独的字符,并依次与DNAKeys进行比较.如果每个字符都匹配,则输出将与输入相同.否则,输出将是匹配的最长的输入起始段,加上结尾的DNAKeys个字符.因此,错误的输入将导致"接近"的良好输出,从而有望提供有用的错误消息.

然后你可以写const v: ValidDNACombinations<"CAT"> = "CAT"个,但这是多余的.因此,我们可以将辅助函数asDNACombinationsinfers作为类型参数.就像这样:

const asDNACombinations = <T extends string>(
    t: T extends ValidDNACombinations<T> ? T : ValidDNACombinations<T>
) => t;

这有点复杂,因为对于像<T extends ValidDNACombinations<T>>(t: T) => t这样更直接的递归约束,TypeScrip会犹豫不决.不管怎样,让我们来测试一下:

const okay = asDNACombinations("GATTACA");

const bad = asDNACombinations("ATTACKING"); // error
// Argument of type '"ATTACKING"' is not assignable to parameter of type
// '"ATTACA" | "ATTACG" | "ATTACC" | "ATTACT"'.

因此,好的输入被接受,而坏的输入被拒绝,并显示一条显示问题的错误消息.


注意,这仍然不会给您提供所需的行为,即将字符串拆分为字符以生成一个由DNAKey个值组成的array.编译器不能执行更高阶的推理来知道这一点.我甚至不确定这对于正则表达式类型是否可行.所以你需要一个type assertion左右的数字来告诉编译器这一点:

const doSomethingWithDNA = <T extends string>(
    dnaString: T extends ValidDNACombinations<T> ? T : ValidDNACombinations<T>) => {
    for (const letter of dnaString) {
        console.log(dna[letter as DNAKeys]); // <-- assertion needed
    }
}

但至少从呼叫者的Angular 来看,它是有效的:

doSomethingWithDNA("CAT"); // okay
doSomethingWithDNA("DOG"); // error

最后要注意的是,在第a write-up in microsoft/TypeScript#41160节中提到:只有当您的数据是static,以编译时已知的字符串literal types的形式,所以您的打字代码看起来像doSomethingWithDNA("CAT"),其中"CAT"字符串直接在代码中时,这些事情才真正有帮助.另一方面,如果您的数据是dynamic,并且从来没有直接出现在打字代码中,那么您的打字代码看起来像doSomethingWithDNA(getDNAFromSomewhere()),其中getDNAFromSomewhere()返回类型DNACombinations的值,而不是字符串文字类型,那么它真的没有任何用处.您不妨只给DNACombinations一个名义上类似的类型,如下面的branded type:

type DNACombinations = string & { __validDNA: true } & Iterable<DNAKeys>;
declare function getDNAFromSomewhere(): DNACombinations;

那么您的doSomethingWithDNA实现即使在没有断言的情况下也能很好地工作(因为我说它是Iterable<DNAKeys>):

const doSomethingWithDNA = (dnaString: DNACombinations) => {
    for (const letter of dnaString) {
        console.log(dna[letter]); // okay
    }
}

然后,只要您不关心硬编码的字符串文字,您就会很高兴:

doSomethingWithDNA(getDNAFromSomewhere()) // okay

doSomethingWithDNA("CAT"); // error, but who cares, you won't do this

这不需要正则表达式类型或泛型或任何东西,您现在就可以这样做.

Playground link to code

Typescript相关问答推荐

在Switch陈述中输入保护类

Angular 17 Select 错误core.mjs:6531错误错误:NG 05105:发现意外合成监听器@transformPanel.done

在Angular中,如何在文件上传后清除文件上传文本框

忽略和K的定义

如何在使用`Next—intl`和`next/link`时导航

如何使函数接受类型脚本中具有正确类型的链接修饰符数组

React重定向参数

角效用函数的类型推断

<;T扩展布尔值>;

TS如何不立即将表达式转换为完全不可变?

是否可以针对联合类型验证类型属性名称?

如何从扩展接口推断泛型?

返回嵌套props 的可变元组

如何在省略一个参数的情况下从函数类型中提取参数类型?

为什么上下文类型在`fn|curred(Fn)`的联合中不起作用?

内联类型断言的工作原理类似于TypeScrip中的断言函数?

我的类型谓词函数不能像我预期的那样工作.需要帮助了解原因

如何填写Partial的一些属性并让类型系统知道它?

我如何键入它,以便具有字符串或数字构造函数的数组可以作为字符串或数字键入s或n

是否使用类型将方法动态添加到类?