有没有一种方法可以在打字稿中为具有唯一项的数组定义类型? [英] Is there a way to define type for array with unique items in typescript?

查看:77
本文介绍了有没有一种方法可以在打字稿中为具有唯一项的数组定义类型?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

类型应该检测数组是否有重复的项目并在打字稿中引发错误?

type UniqueArray = [
  // How to implement this?
]

const a:UniqueArray = [1, 2, 3] // success
const b:UniqueArray = [1, 2, 2] // error

PS:我目前正在使用JS删除重复项,但是,好奇是否可以使用手写体类型先捕获此错误?

解决方案

在编译时唯一可行的方法是如果您的数组是


如果您正在获取文字的元组...比如说,从使用代码作为库的开发人员那里,那么您就有机会获得有用的东西,但是它很复杂,而且可能很脆弱.这是我的处理方式:

首先,我们提出一种类似于无效类型的行为,而TypeScript不会这样做.没有.这个想法是无法分配任何值的类型(例如 never ),但在编译器遇到该错误消息时会生成自定义错误消息.以下内容并非十全十美,但是如果您斜视,它会产生可能合理的错误消息:

type Invalid<T> = Error & { __errorMessage: T };

现在我们代表UniqueArray.它不能作为具体类型来完成(因此没有const a: UniqueArray = ...),但是我们可以将其表示为

使用了大量映射条件类型,但它实际上遍历了数组并查看该数组中的其他任何元素是否与当前元素匹配.如果是这样,则会出现错误消息.

现在使用辅助功能.另一个难题是,默认情况下,像doSomething([1,2,3])这样的函数会将[1,2,3]视为number[]而不是文字的[1,2,3]元组.没有处理此问题的简单方法,因此我们必须使用怪异的魔术(请参阅讨论该魔术的链接):

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"]>

无论如何,所有这一切对您来说都不值得,但我想证明类型系统可以提供某种接近该目标的方法.希望能有所帮助;祝你好运!

解决方案

The only possible way this could work at compile time is if your arrays are tuples composed of literals. For example, here are some arrays with the same runtime values, but with different types in 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];

Only the first one would behave as you'd like... the compiler would realize that tupleOfLiterals has exactly 3 elements, two of which are the same type. In all the other cases, the compiler doesn't understand what's going on. So if you're passing arrays you get from other functions, or from an API, etc., and the type of these arrays is something like number[], then the answer is just "no, you can't do this".


If you're getting tuples of literals... say, from a developer using your code as a library, then you have a chance of getting something that works, but it's complex and possibly brittle. Here's how I might do it:

First we come up something that acts like an invalid type, which TypeScript doesn't have. The idea is a type to which no value can be assigned (like never) but which produces a custom error message when the compiler encounters it. The following isn't perfect, but it produces error messages that are possibly reasonable if you squint:

type Invalid<T> = Error & { __errorMessage: T };

Now we represent UniqueArray. It can't be done as a concrete type (so no const a: UniqueArray = ...) but we can represent it as a generic constraint that we pass to a helper function. Anyway, here's AsUniqueArray<A> which takes a candidate array type A and returns A if it is unique, and otherwise returns a different array where there are error messages in the places that are repeated:

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]
};

That uses a lot of mapped and conditional types, but it essentially walks through the array and looks to see if any other elements of the array match the current one. If so, there's an error message.

Now for the helper function. Another wrinkle is that by default, a function like doSomething([1,2,3]) will treat [1,2,3] as a number[] and not a [1,2,3] tuple of literals. There's no simple way to deal with this, so we have to use weird magic (see the link for discussion of that magic):

type Narrowable =
  | string
  | number
  | boolean
  | object
  | null
  | undefined
  | symbol;

const asUniqueArray = <
  N extends Narrowable,
  A extends [] | ReadonlyArray<N> & AsUniqueArray<A, A>
>(
  a: A
) => a;

Now, asUniqueArray() just returns its input at runtime, but at compile time it will only accept array types it perceives as unique, and it will put errors on the problem elements if there are repeats:

const okay = asUniqueArray([1, 2, 3]); // okay

const notOkay = asUniqueArray([1, 2, 2]); // error!
//                                ~  ~
// number is not assignable to Invalid<[2, "is repeated"]> | undefined

Hooray, that is what you wanted, right? The caveats from the beginning still hold, so if you end up getting arrays that are already widened (either non-tuples or non-literals), you'll have undesirable behavior:

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"]>

Anyway, all this might not be worth it for you, but I wanted to show that there is kind of, sort of, maybe, a way to get close to this with the type system. Hope that helps; good luck!

Link to code

这篇关于有没有一种方法可以在打字稿中为具有唯一项的数组定义类型?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆