如果你真的想在类型系统中表示“没有重复元素的数组”,你最好的选择是作为generic 类型,充当constraint。下面是我的开始:
type RemoveArrayRepeats<T extends readonly any[]> = {
[K in keyof T]: (
T[number] extends { [P in keyof T]: P extends K ? never : T[P] }[number]
? never
: T[K]
)
}
因此,RemoveArrayRepeats 类型采用 tuple 类型并将其转换为自身的版本,其中任何重复的元素都将替换为 never 类型。像这样:
type TestSame = RemoveArrayRepeats<[1, 2, 3, 4, 5]>; // [1, 2, 3, 4, 5]
type TestDiff = RemoveArrayRepeats<[1, 2, 3, 2, 5]>; // [1, never, 3, never, 5]
它是如何工作的草图:给定一个元组类型T,它maps over每个类似数字的索引K,并将T(即T[number])的元素类型的并集与T 的元素类型的联合,没有索引 K 处的元素(即 {[P in keyof T]: P extends K ? never: T[P]}[number].)。如果它们不同,那就太好了,不要管这个属性。如果它们相同,我们有一个重复...替换为never。
以[1,2,3,2,5]为例:如果K是"0",那么我们比较[1,2,3,2,5]的元素联合,即1|2|3|5和[never,2,3,2,5]的联合,即2|3|5。由于它们不同,因此元组的第一个元素不是重复的,它被单独留下:1。现在我们看K是"1"。我们比较1|2|3|5和[1,never,3,2,5]的元素,仍然是1|2|3|5。它们是一样的,意味着元组的第二个元素将被替换为never。最终我们建立了数组[1,never,3,never,5]。
接下来我们将getGoogleApiUrl 函数声明为泛型函数,其中libraries 参数被限制为AllMapLibraries[] 和RemoveArrayRepeats<T>:
declare const getGoogleApiUrl: <T extends AllMapLibraries[]>(
apiKey: string,
libraries: (T & RemoveArrayRepeats<T>) | []
) => string;
约束如何发生的细节有点奇怪。我选择将T & RemoveArrayRepeats<T> 放在参数位置,这样T 本身只会被限制为AllMapLibraries[];这样可以使错误消息(例如'drawink')保持可以理解。但是您可以改为使用T extends AllMapLibraries[] & RemoveArrayRepeats<T> 来获得非常相似的行为。
哦,libraries 类型中的| [] 是一个trick,它给编译器一个提示,如果可能的话,我们想为libraries 推断一个元组类型。没有它,编译器会将["drawing", "drawing", "drawing"] 视为Array<"drawing"> 类型,这将完全丢失我们检测重复所需的信息。
让我们看看它是否有效:
getGoogleApiUrl('', []); // okay
getGoogleApiUrl('', ['drawing']); // okay
getGoogleApiUrl('', ['drawing', 'geometry']); // okay
getGoogleApiUrl('', ['drawing', 'geometry', 'places', 'visualization']); // okay
getGoogleApiUrl('', ['foo']); // error!
// ~~~~~
// Type '"foo"' is not assignable to type 'AllMapLibraries'
getGoogleApiUrl('123', ['drawing', 'geometry', 'drawing']); // error!
// ~~~~~~~~~ ~~~~~~~~~
// Type 'string' is not assignable to type 'undefined'
这一切都或多或少符合预期。最后一种情况下的错误消息(如“string is not undefined”)不是您在完美世界中可能希望看到的。如果有支持的custom invalid type,我们将使用它来定制错误消息。但是没有。
至少红色波浪线位于有意义的位置。好吧,也许您只希望其中 一个 是错误的;但这会使RemoveArrayRepeats 更加复杂(例如,仅将一个元素与较低索引的元素进行比较,但编译器不理解“较低的索引”,因此我们需要一些表示,它会很快变得丑陋)。
但这是我可以在没有代码页的情况下最接近您的要求。这个版本是否值得由你决定。无论如何,希望有所帮助;祝你好运!
Link to code