如何告诉 TypeScript 允许列表中的多个字符串,每个字符串在扩展参数中只允许一次?

yah*_*rga 5 typescript

我有一个可以提供给函数的参数列表'one' | 'two' | 'three' | 'four' | 'five'。我希望每个只使用一次,如下所示:

function foo(...x: ('one' | 'two' | 'three' | 'four' | 'five')[]) {
  // do something...
}

foo('one', 'five', 'three', 'five'); // This works, even though I want TypeScript to say this isn't allowed.
Run Code Online (Sandbox Code Playgroud)

jca*_*alz 2

评论是正确的:几乎不可能保证只有唯一的数组才能通过,因此无论您如何处理输入,都应该有运行时代码来消除传入的任何数组的重复项。

不过,您可能希望键入内容向用户提示您最多只能传入每个参数类型一次。由于 TypeScript 4.1 对递归条件类型的支持,这是可能的,甚至不是完全疯狂:

type UniqueFrom<T extends any[], U> =
  U[] extends T ? T :
  T extends [infer F, ...infer R] ?
  [U, ...UniqueFrom<R, Exclude<U, F>>] : []

type Vals = 'one' | 'two' | 'three' | 'four' | 'five';

function foo<T extends Vals[]>(...t: UniqueFrom<T, Vals>) { }
Run Code Online (Sandbox Code Playgroud)

这个想法是UniqueFrom<T, Vals>应该生成一个与 长度相同的元组,显示当您从左到右遍历时,每一步中T哪些元素仍然可用。例如:ValsT

type U = UniqueFrom<["one", "two", "three"], Vals>;
// [Vals, "two" | "three" | "four" | "five", "three" | "four" | "five"]
Run Code Online (Sandbox Code Playgroud)

这表示 的第一个元素T可以是以下任意内容Vals;一旦第一个元素被选择为"one",现在下一个元素可以是除了Vals之外的任何元素"one"。当第二个元素被选择为 时"two",现在最后一个元素可以是"three""four"或中的任何元素"five"

只要T可以分配给UniqueFrom<T, Vals>,那么编译器就会接受类型的元组T作为 的剩余参数foo()。让我们看看它是否有效:

foo("one", "two", "three", "four", "five"); // okay
foo("two", "one", "six", "five"); // error! 
// -------------> ~~~~~
// type '"six"' is not assignable to type 'Vals'
foo("one", "two", "three", "two", "five"); // error!
// ----------------------> ~~~~~
// type '"two"' is not assignable to type '"four" | "five"'
Run Code Online (Sandbox Code Playgroud)

这对我来说看起来很合理。编译器会抱怨第二个参数名为"two",并告诉您它需要其中一个"four""five"一个。

Playground 代码链接