TypeScript:<type> 的字符串

标签 typescript types

也许我正在失去它。

我有可能的核苷酸列表和相应的类型:

const DNA = ['G', 'C', 'T', 'A'] as const;

type DNA = typeof DNA[number];

现在,DNA 可以是任意字符 (GCTA) 的任意组合、任意长度的字符串。这里重要的是,每个单独的角色都是 DNA 类型。

如果我split('')这个strand,我想得到一个字符数组,每个字符的类型为DNA ,例如,

const strand: ??? = "GACATAGACGCGTTAG";
const DNAstring: DNA[] = strand.split('');

在这种情况下如何输入strand? 🤯

如果我将其输入为字符串,TypeScript 会感到不安,这是可以理解的:类型 'string[]' 不能分配给类型 '("G"| "C"| "T"| "A")[]'.

非常感谢任何帮助!

最佳答案

不幸的是,TypeScript 中没有特定类型来表示仅由某个集合中的字符组成的字符串。有一个长期建议支持regular expression已验证的字符串类型;请参阅microsoft/TypeScript#41160关于该主题当前 Unresolved 问题。如果存在这样的类型,你可能会写

// NOT VALID TS, don't do this:
type DNAStrand = /[GCTA]*/;

然后就完成了。唉,这是不可能的(还?)。如果您想(稍微)增加将来实现此功能的机会,您可以转到该问题,给它一个👍,并描述您的用例,为什么它引人注目(有人告诉我 DNA 链不是主要的大多数人的用例),以及为什么当前的解决方案(我在下面介绍一些)还不够。


目前,TypeScript 有 template literal types ,它允许您对字符串 literal types 执行一些按字符操作。 。人们可能会天真地尝试表示 DNAStrand作为union type空字符串加上一个 DNA接下来是另一个 DNAStand ...有点像BNF语法表示:

// NOT VALID TS, don't do this:
type DNAStrand = "" | `${DNA}${DNAStrand}` // can't do this

唉,这被视为无效的循环;联合体是急切计算的,编译器会在某个时候崩溃,所以它会提示。

可以使用 recursive conditional type 将所有有效的 DNA Strand 字符串文字联合起来,达到一定的长度。 :

type RepeatLessThan<N extends number, T extends string, A extends string[] = [""]> =
  N extends A['length'] ? A[number] : RepeatLessThan<N, T, [`${T}${A[0]}`, ...A]>

// and this works for short lengths
type DNAStandUpToThree = RepeatLessThan<4, DNA>;
/* type DNAStandUpToThree = "" | "G" | "C" | "T" | "A" | "GG" | "GC" | "GT" | "GA" | "CG" | "CC" | "CT" | 
 "CA" | "TG" | "TC" | "TT" | "TA" | "AG" | "AC" | "AT" | "AA" | "GGG" | "GGC" | "GGT" | "GGA" | "GCG" | "GCC" 
 | "GCT" | "GCA" | "GTG" | "GTC" | "GTT" | "GTA" | "GAG" | "GAC" | "GAT" | "GAA" | "CGG" | "CGC" | "CGT" | 
 "CGA" | "CCG" | "CCC" | "CCT" | "CCA" | "CTG" | "CTC" | "CTT" | "CTA" | "CAG" | "CAC" | "CAT" | "CAA" | 
 "TGG" | "TGC" | "TGT" | "TGA" | "TCG" | "TCC" | "TCT" | "TCA" | "TTG" | "TTC" | "TTT" | "TTA" | "TAG" | 
 "TAC" | "TAT" | "TAA" | "AGG" | "AGC" | "AGT" | "AGA" | "ACG" | "ACC" | "ACT" | "ACA" | "ATG" | "ATC" | 
 "ATT" | "ATA" | "AAG" | "AAC" | "AAT" | "AAA" */

但是 TypeScript 只能表示最多包含 100,000 个成员的联合体。您的示例字符串有 16 个字符长,因此您至少需要 4^16 ≈ 40 亿个成员来表示它。没有有用的方法来枚举这些。如果你尝试,你会遇到问题:

// VALID TS, but STILL DON'T DO THIS:
type DNAStandUpToSixteen = RepeatLessThan<17, DNA> // 🔥💻🔥, eventually error with
// "Expression produces a union type that is too complex to represent"

相反,我们可以使用模板文字类型得到的最接近的结果是 DNAStrand一个generic输入验证检查字符串文字以查看其是否有效。这使我们不必枚举每个可能的有效链,但它的缺点是处理这些类型的任何内容本身都需要是通用的:

type VerifyDNAStrand<T extends string, A extends string = ""> =
  T extends `${infer F}${infer R}` ? 
    F extends DNA ? VerifyDNAStrand<R, `${A}${F}`> : `${A}${DNA}` : 
  A

你不能轻易地写const x: DNAStrand = "ACT" ,您需要const x: DNAStrand<"ACT"> = "ACT" 。因此,使用通用辅助标识函数比注释更容易:

const dnaStrand = <T extends string>(
  x: T extends VerifyDNAStrand<T> ? T : VerifyDNAStrand<T>) => x;

const goodStrand = dnaStrand("GATTACA"); // okay
const badStrand = dnaStrand("ATTACK OF THE CLONES"); // error
// -----------------------> ~~~~~~~~~~~~~~~~~~~~~~
// Argument of type '"ATTACK OF THE CLONES"' is not assignable to parameter of type 
// '"ATTACA" | "ATTACG" | "ATTACC" | "ATTACT"'

那太好了。但是编译器的definition for String.prototype.split() 就像

interface String {
    split(separator: string | RegExp, limit?: number): string[];
}

始终输出 string[]无论。编译器不知道"ABC".split("")产生["A", "B", "C"] 。所以以上所有内容都适用于 DNAStrand不能解决您的根本问题:

const stillNotDNAstring: DNA[] = goodStrand.split(''); // error!

所以你又需要 type assertion某处:

const dnaString = goodStrand.split('') as DNA[]; // okay

但这仅仅意味着在类型安全方面您已经从编译器手中接管了。没有什么可以阻止你做错事:

const oops = badStrand.split('') as DNA[]; // still okay

也许您可以将拆分代码包装到一个函数中,其中函数内部的类型安全需要手动处理,但人们至少可以安全地调用它,有点:

function splitStrand<T extends string>(
  x: T extends VerifyDNAStrand<T> ? T : VerifyDNAStrand<T>) {
  return x.split('') as DNA[];
}

const okay = splitStrand("GATTACA"); // okay
const bad = splitStrand("ATTACK OF THE CLONES"); // compiler error

因此,当涉及到 DNA 链字符串的编译时验证时,这是我能想到的最接近的结果。如果您不需要编译时验证,而只是需要某种方式来跟踪类型,则可以使用带有品牌类型或其他结构的运行时验证,如 the other answer详细信息。


Playground link to code

关于TypeScript:<type> 的字符串,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/72719835/

相关文章:

typescript - 如何在 TypeScript 中定义通用对象数组,每个项目都有不同的模板参数

javascript - 将 Scanner-js 与 Angular2 结合使用

c++ - 使用 typedef 自定义内置类型

haskell - 使用 LLVM/Haskell 的 CodeGenFunction/CodeGenModule 的类型问题

scala - 类型构造函数的类型边界中的下划线

C# 如何将对象列表传递给构造函数?

java - 验证 Java 中反射方法的返回类型和参数

typescript - 如何通过启动 adonis 服务器来启动调度程序

angular - 使用 Angular $interval 按时间间隔更新 TextView

javascript - 删除请求不起作用