【问题标题】:Typescript: only allow one occurrence of a value in an array打字稿:只允许在数组中出现一次值
【发布时间】:2021-01-20 05:40:51
【问题描述】:

有没有一种简单的方法可以只允许一个值在数组中出现一次?

假设我已经定义了这样的类型:

  array: {
    value: boolean;
    label: string;
  }[];

我想确保在该数组中,只有一个值可以为真。如果一个值为真,则其他值必须为假。因此,例如,我想允许这样做:

const arr : array = [
    { value: false, label: '1' },
    { value: true, label: '2' },
    { value: false, label: '3' },
  ];

但我希望 typescript 失败,并说如果我有这样的数组,则只有一个值可以为真:

const arr2 : array = [
    { value: true, label: '1' },
    { value: true, label: '2' },
    { value: false, label: '3' },
  ];

这在打字稿中可行吗?

编辑:可以肯定的是,我只想通过类型来确保这一点,而不是其他逻辑

【问题讨论】:

  • 不,我认为这在类型系统中是不可能的。
  • 不是天生的,但是您可以使用在索引处设置value: true 的方法构建一个类。该方法可以先检查是否违反规则并抛出错误。
  • 您的阵列是否始终具有固定的小尺寸?
  • 数组大小不固定,但可能永远不会超过10个元素

标签: javascript typescript


【解决方案1】:

在类型级别(至少在 TypeScript 中)这样做是可能的,但在现实世界中并没有真正有用。你最好的选择是只使用内置的Set 来处理简单的值或使用深度相等的 Set 库(例如immutable-js),这将允许你按插入顺序迭代并强制所有项目都是唯一的。


为了给出一个可以使用类型检查器的答案:

可以使用类型化条件将类型返回为never,从而确保程序不会编译。但是,您需要在编译时知道这些值才能使用。

对于 TS 没有相等运算符的事实,我们可以使用一种解决方法,使用此处描述的技术:https://stackoverflow.com/a/53808212/7042389。然后我们可以使用它来构建一个唯一类型的数组,如果一个与另一个匹配,则其类型返回为never - 这将导致编译错误,因为不能为 never 分配任何值。

type IfNotEq<A, B, T> =
    (<T>() => T extends A ? 1 : 2) extends
    (<T>() => T extends B ? 1 : 2) ? never : T;

type UniqueArray2<A, B> = IfNotEq<A, B, [A, B]>;
type UniqueArray3<A, B, C> = IfNotEq<A, B, IfNotEq<B, C, IfNotEq<A, C, [A, B, C]>>>;

const xs1: UniqueArray2<"a", "b"> = ["a", "b"]; // works
const xs2: UniqueArray2<"a", "a"> = ["a", "a"]; // string is not assignable to never

const ys1: UniqueArray3<"a", "b", "c"> = ["a", "b", "c"];
const ys2: UniqueArray3<"a", "a", "b"> = ["a", "b", "c"]; // string is not assignable to never

Playground Link

【讨论】:

    【解决方案2】:

    我认为,唯一的方法是为每种可能的情况编写一份详尽的清单。您在列表中提到永远不会超过 10 - 如果您可以制定严格的规则,那么您可以执行以下操作:

     type Array1 = {
      value: boolean;
      label: string;
    }[];
    
    type ArrayItem<T extends boolean> = {
      value: T,
      label: string;
    }
    
    type Array2 = [
      ArrayItem<true>,
      ArrayItem<false>,
      ArrayItem<false>,
    ] | [
      ArrayItem<false>,
      ArrayItem<true>,
      ArrayItem<false>,
    ] | [
      ArrayItem<false>,
      ArrayItem<false>,
      ArrayItem<true>,
    ];
    
    const x: Array1 = [
      { value: true, label: '' },
      { value: true, label: '' },
      { value: true, label: '' }
    ]
    
    const y: Array2 = [
      { value: false, label: '' },
      { value: false, label: '' },
      { value: true, label: '' }
    ];
    
    // only z has a type error
    const z: Array2 = [
      { value: true, label: '' },
      { value: false, label: '' },
      { value: true, label: '' }
    ]
    

    Playground Link

    【讨论】:

    • 嗯,我明白你的意思,但我不认为这适用于我的用例,即使它可能更新会那么大,但我不能强制执行最多 10 个元素。感谢您的意见! :D
    • @dtd 好的 - 我认为没有其他解决方案(我尝试了一些),所以看起来你可能不走运。祈祷有人可以加入以提供更好的解决方案
    【解决方案3】:

    有没有一种简单的方法可以只允许一个值在 数组?

    取决于您的要求。

    这是我能想到的最简单/最易读的方法,但它只允许数组的第一个元素为真,其他所有元素都必须为假。

    type normalObj = {
      value: false,
      label: string
    }
    
    type exceptionObj = {
      value: true,
      label: string
    }
    
    type normalObjWithOneException = [exceptionObj, ...normalObj[]];
    
    const arr : normalObjWithOneException = [
        { value: true, label: '1' },
        { value: false, label: '2' },
        { value: false, label: '3' },
        { value: false, label: '4' },
        { value: false, label: '5' },
      ];
    

    Playground Link

    如果您希望提供与订单无关的类型,这可能是一个限制。但如果我们从反转元组顺序开始,那么答案是,这是一种“简单的方法” 到目前为止,TypeScript 中不存在(在 JS 中,我们有很多选择:集合、错误、代理......)。

    如果您关心与顺序无关的类型,这是一个已经讨论过很多的话题(关于反转元组):

    1. how to write an Invert type in typescript to invert the order of tuples
    2. Unordered tuple type
    3. Typescript: Unsorted tuple

    【讨论】:

      猜你喜欢
      • 2022-01-24
      • 1970-01-01
      • 2019-07-28
      • 2023-01-04
      • 2013-08-05
      • 1970-01-01
      • 2020-08-11
      • 2021-12-03
      • 1970-01-01
      相关资源
      最近更新 更多