类型应该检测数组是否有重复的项目并在打字稿中引发错误?
type UniqueArray = [
// How to implement this?
]
const a:UniqueArray = [1, 2, 3] // success
const b:UniqueArray = [1, 2, 2] // error
PS:我目前正在使用JS删除重复项,但是,好奇是否可以事先使用打字稿类型捕获此错误?
答案 0 :(得分:3)
是的! TypeScript 4.1有一种方法(在撰写本文时为beta)。这是这样的:
const data = ["11", "test", "tes", "1", "testing"] as const
const uniqueData: UniqueArray<typeof data> = data
type UniqueArray<T> =
T extends readonly [infer X, ...infer Rest]
? InArray<Rest, X> extends true
? ['Encountered value with duplicates:', X]
: readonly [X, ...UniqueArray<Rest>]
: T
type InArray<T, X> =
T extends readonly [X, ...infer _Rest]
? true
: T extends readonly [X]
? true
: T extends readonly [infer _, ...infer Rest]
? InArray<Rest, X>
: false
如果相同的值出现多次,则会出现编译器错误。
答案 1 :(得分:1)
Typescript仅在类型上引用,而不在值上引用,而且在运行时数组上将不执行任何操作。
答案 2 :(得分:1)
Typescript仅执行编译时检查。 Typescript无法检测到在运行时修改数组。 另一方面,您可能想使用Set类来防止插入重复项(但除非您检查返回值,否则不会引发错误)。但这不会引起编译时错误。
答案 3 :(得分:1)
在编译时唯一可行的方法是,如果您的数组是由tuples组成的literals。例如,以下是一些具有相同运行时值但TypeScript中具有不同类型的数组:
const tupleOfLiterals: [1, 2, 2] = [1, 2, 2];
const tupleOfNonLiterals: [number, number, number] = [1, 2, 2];
const arrayOfLiterals: (1 | 2)[] = [1, 2, 2];
const arrayOfNonLiterals: number[] = [1, 2, 2];
只有第一个会表现出您想要的行为……编译器会意识到tupleOfLiterals
恰好具有3个元素,其中两个是同一类型。在所有其他情况下,编译器都不了解发生了什么。因此,如果要传递从其他函数或API等获得的数组,并且这些数组的类型类似于number[]
,那么答案就是“不,您不能这样做“。
如果您正在获取文字的元组...比如说,从使用代码作为库的开发人员那里,那么您就有机会获得有用的东西,但是它很复杂,而且可能很脆弱。这是我的处理方式:
首先,我们提出一种类似于invalid type的行为,而TypeScript则没有。这个主意是一种不能分配任何值的类型(例如never
),但是当编译器遇到该错误类型时会生成自定义错误消息。以下内容并非十全十美,但是如果您斜视,它会产生可能合理的错误消息:
type Invalid<T> = Error & { __errorMessage: T };
现在,我们代表UniqueArray
。它不能作为具体类型完成(所以没有const a: UniqueArray = ...
),但是我们可以将其表示为generic constraint,并将其传递给辅助函数。无论如何,这里的AsUniqueArray<A>
采用候选数组类型A
并返回A
(如果它是唯一的),否则返回另一个数组,该数组中重复出现错误消息的地方:< / p>
type AsUniqueArray<
A extends ReadonlyArray<any>,
B extends ReadonlyArray<any>
> = {
[I in keyof A]: unknown extends {
[J in keyof B]: J extends I ? never : B[J] extends A[I] ? unknown : never
}[number]
? Invalid<[A[I], "is repeated"]>
: A[I]
};
这使用了大量的mapped和conditional类型,但实际上它遍历了数组,并查看该数组中是否还有其他元素与当前元素匹配。如果是这样,则会出现错误消息。
现在使用辅助功能。另一个难题是,默认情况下,像doSomething([1,2,3])
这样的函数会将[1,2,3]
视为number[]
而不是文字的[1,2,3]
元组。没有simple way to deal with this,因此我们必须使用奇怪的魔术(请参阅有关该魔术的讨论链接):
type Narrowable =
| string
| number
| boolean
| object
| null
| undefined
| symbol;
const asUniqueArray = <
N extends Narrowable,
A extends [] | ReadonlyArray<N> & AsUniqueArray<A, A>
>(
a: A
) => a;
现在,asUniqueArray()
仅在运行时返回其输入,但是在编译时,它将仅接受它认为唯一的数组类型,并且如果存在重复,则会在问题元素上出现错误:
const okay = asUniqueArray([1, 2, 3]); // okay
const notOkay = asUniqueArray([1, 2, 2]); // error!
// ~ ~
// number is not assignable to Invalid<[2, "is repeated"]> | undefined
万岁,这就是您想要的,对吧?从一开始的警告仍然存在,因此,如果最终得到的数组已经被扩大了(非元组或非文字),则将出现不良行为:
const generalArray: number[] = [1, 2, 2, 1, 2, 1, 2];
const doesntCareAboutGeneralArrays = asUniqueArray(generalArray); // no error
const arrayOfWideTypes: [number, number] = [1, 2];
const cannotSeeThatNumbersAreDifferent = asUniqueArray(arrayOfWideTypes); // error,
// Invalid<[number, "is repeated"]>
无论如何,所有这一切对您来说都不值得,但我想表明,有某种方法可以接近类型系统。希望能有所帮助;祝你好运!