如果没有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 types将T
解析为单独的字符,并依次与DNAKeys
进行比较.如果每个字符都匹配,则输出将与输入相同.否则,输出将是匹配的最长的输入起始段,加上结尾的DNAKeys
个字符.因此,错误的输入将导致"接近"的良好输出,从而有望提供有用的错误消息.
然后你可以写const v: ValidDNACombinations<"CAT"> = "CAT"
个,但这是多余的.因此,我们可以将辅助函数asDNACombinations
和infers作为类型参数.就像这样:
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个