我想定义一个类型,其中Array可以包含特定字符串列表之一,例如:
const foo = "foo";
const bar = "bar';
const baz = "baz':
// acceptable
[foo, bar]
// acceptable
[foo, bar, baz]
// unacceptable
[foo, foo]
// unacceptable
[foo, bar, bar]
我该怎么做?
答案 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 types将keyof 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"
现在,如果要传递数组类型,则您不想检查每个可能的属性是否重复,因为数组具有forEach
和map
之类的方法以及{{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]
所以,一切正常。好极了!好极了?如您所见,这里有很多警告和杂耍类型。对于在任何人的代码中使用,我不是特别满意,但对于我自己的东西,至少在没有大量测试的情况下。
无论如何,希望能给您一些想法。祝你好运!