定义由特定字符串之一组成的TypeScript数组

时间:2018-07-14 13:06:01

标签: typescript

我想定义一个类型,其中Array可以包含特定字符串列表之一,例如:

const foo = "foo";
const bar = "bar';
const baz = "baz':

// acceptable
[foo, bar]

// acceptable 
[foo, bar, baz]

// unacceptable
[foo, foo]

// unacceptable
[foo, bar, bar]

我该怎么做?

1 个答案:

答案 0 :(得分:4)

我绝对喜欢这种问题,即使答案的形式为“您可以进行后空翻并接近解决方案,但这可能不值得”。这个答案是哪个。让我们做一些后空翻!

首先,尝试表示约束NoRepeats<T>,其中如果T具有所有不同的属性类型,则输出将为T,但是即使两个键具有相同的属性类型,输出将为never

type NoRepeats<T> = true extends (
  (keyof T) extends (infer K) ? K extends any ? (
    T[K] extends T[Exclude<keyof T, K>] ? true : never
  ) : never : never
) ? "No Repeats Please" : T

它使用distributive conditional typeskeyof T拉成单个键K,然后将T[K]T[Exclude<keyof T, K>]进行比较,其中Exclude<keyof T, K>的意思是“全部K”以外的其他键。如果它们匹配,那么整个事情将是"No Repeats Please"(代替invalid types的那种穷人的错误消息)。如果它们都不匹配,则答案为T

让我们看看它的行为:

interface X {a: string, b: number, c: boolean}; // no repeats
declare const x: NoRepeats<X>; // x is of type X

interface Y {a: string, b: number, c: string}; // a and c are string
declare const y: NoRepeats<Y>; // y is of type "No Repeats Please"

现在,如果要传递数组类型,则您不想检查每个可能的属性是否重复,因为数组具有forEachmap之类的方法以及{{1}这样的属性}。而且,您实际上只是在尝试仅检查tuples,而不是常规数组。那是因为所有编译器对数组的了解都是其所有元素类型的并集,并且它无法确定是否存在重复项。因此,您只需要检查元组的类似数字的键(length"0""1",直到但不包括元组的长度)。

因此,我们需要拉出所有数组类型共有的所有键...我将其称为“剥离”元组:

"2"

并确保我们仅检查元组而不检查通用数组:

type StripTuple<T extends any[]> = Pick<T, Exclude<keyof T, keyof any[]>>

因此,让我们在元组和数组上尝试一下:

type NoRepeatTuple<T extends any[]> = 
  number extends T['length'] ? "Must Be Tuple" : NoRepeats<StripTuple<T>>

所以我们越来越近了。现在无法声明类型declare const z: NoRepeatTuple<[string, number, boolean]> // {0: string, 1: number, 2:boolean} declare const a: NoRepeatTuple<[string, number, string]> // "No Repeats Please" declare const b: NoRepeatTuple<string[]> // "Must Be Tuple" T,因为这是循环约束。但是,通过创建一个仅接受不重复的元组的函数,您可以做类似于近似exact types的技巧:

NoRepeatTuple<T>

将参数function requireNoRepeats<V extends "foo" | "bar" | "baz", T extends Array<V>>( t: T & NoRepeatTuple<T> ) { } 键入为t将使编译器推断以T & NoRepeatTuple<T>传递的t的类型,然后使用{{1}进行检查}。让我们看看是否可以尝试:

T

哇!哦,问题在于值NoRepeatTuple<T>被推断为数组而不是元组。那是TypeScript中的issue,我认为它不会立即修复。

因此,我们必须说服编译器将某些内容解释为元组。 way I've used before的目的是编写一个推断元组类型的函数。当TS3.0即将登陆时,您可以使用tuple rest/spread作为

来简洁地编写它
const foo = "foo";
const bar = "bar";
const baz = "baz";
requireNoRepeats([foo, bar]) // error, must be tuple

在此之前,您可以使用a more verbose function

最后,让我们尝试一下:

[foo, bar]

所以,一切正常。好极了!好极了?如您所见,这里有很多警告和杂耍类型。对于在任何人的代码中使用,我不是特别满意,但对于我自己的东西,至少在没有大量测试的情况下。

无论如何,希望能给您一些想法。祝你好运!