具有独特属性的TypeScript对象集

问题描述 投票:0回答:3

我写了这样的简单接口:

interface IProduct {
  id: number;
  name: string;
  price: number;
  description?: string;
}

现在我想

id
在 ReadonlyArray 中是唯一的。因此,当创建一些产品时,我想防止添加具有相同 id 的对象。包含产品的数组将在文件中创建一次,并且不会被修改。

你对此有什么想法吗? JS Set 将是一个很好的解决方案,但我无法向它们添加自己的比较器。 请不要提供需要额外框架等的解决方案。

javascript typescript set
3个回答
5
投票

此示例使用类型系统来静态验证是否存在重复。


interface IProduct<Id extends number> {
    id: Id
    name: string;
}

const product = <Id extends number>(id: Id, name: string) => ({ id, name })

type Validation<
    Products extends IProduct<number>[],
    Accumulator extends IProduct<number>[] = []>
    =
    (Products extends []
        // #1 Last call
        ? Accumulator
        // #2 All calls but last
        : (Products extends [infer Head, ...infer Tail]
            ? (Head extends IProduct<number>
                // #3 Check whether [id] property already exists in our accumulator 
                ? (Head['id'] extends Accumulator[number]['id']
                    ? (Tail extends IProduct<number>[]
                        // #4 [id] property is a duplicate, hence we need to replace it with [never] in order to trigger the error
                        ? Validation<Tail, [...Accumulator, { id: never, name: Head['name'] }]>
                        : never)
                    // #5 [id] is not a duplicate, hence we can add to our accumulator whole product
                    : (Tail extends IProduct<number>[]
                        ? Validation<Tail, [...Accumulator, Head]>
                        : never)
                )
                : never)
            : never)
    )

type Ok = Validation<[{ id: 1, name: '1' }, { id: 2, name: '2' }]>
type Fail = Validation<[{ id: 1, name: '1' }, { id: 1, name: '2' }]> // id:never

const builder = <
    Product extends IProduct<number>,
    Products extends Product[]
>(...products: [...Products] & Validation<Products>) => products

builder(product(1, 'John'), product(2, 'Doe'))

游乐场

Validation
- 递归地迭代所有传递到函数产品中的内容。如果累加器类型中已存在
product[id]
- 将
id
属性替换为
never
,否则只需将产品添加到累加器。

请看评论

#1
#2
....

如果您不想使用

rest
运算符,请考虑以下示例:


interface IProduct<Id extends number> {
    id: Id
    name: string;
}

const product = <Id extends number>(id: Id, name: string) => ({ id, name })

type Validation<
    Products extends IProduct<number>[],
    Accumulator extends IProduct<number>[] = []>
    =
    (Products extends []
        // #1 Last call
        ? Accumulator
        // #2 All calls but last
        : (Products extends [infer Head, ...infer Tail]
            ? (Head extends IProduct<number>
                // #3 Check whether [id] property already exists in our accumulator 
                ? (Head['id'] extends Accumulator[number]['id']
                    ? (Tail extends IProduct<number>[]
                        // #4 [id] property is a duplicate, hence we need to replace it with [never] in order to trigger the error
                        ? Validation<Tail, [...Accumulator, { id: never, name: Head['name'] }]>
                        : 1)
                    // #5 [id] is not a duplicate, hence we can add to our accumulator whole product
                    : (Tail extends IProduct<number>[]
                        ? Validation<Tail, [...Accumulator, Head]>
                        : 2)
                )
                : 3)
            : Products)
    )


type Ok = Validation<[{ id: 1, name: '1' }, { id: 2, name: '2' }]>
type Fail = Validation<[{ id: 1, name: '1' }, { id: 1, name: '2' }]> // id:never

const builder = <
    Id extends number,
    Product extends IProduct<Id>,
    Products extends Product[]
>(products: Validation<[...Products]>) => products

builder([product(1, 'John'), product(1, 'John')]) // error

游乐场

如果你对静态验证感兴趣,可以查看我的文章


0
投票

你想要编译时保证吗?我怀疑这在 TypeScript 中是否可行。编辑:也许在看到其他答案后是可能的。

但是,JavaScript 代码非常简单:

let products = new Map();

let product = {
  id: 1,
  name: "Foo",
  price: 99,
  description: "Bar",
};

// This will update an existing item if it has the same ID
products.set(product.id, product);

// Alternatively, you can check if one already exists in keep the original item
if (!products.has(product.id)) {
  products.set(product.id, product);
}

您可以使用一组函数将此代码包装在您自己的类中:

class ProductList {
  constructor() {
    this.items = new Map();
  }

  add(product) {
    if(!this.items.has(product.id)) {
      this.items.set(product.id, product);
    }
  }

  values() {
    // iterate over values
    // this will respect insertion order
    return this.items.values();
  }

  // any other methods you'd like...
}

如果您需要通过索引进行随机访问,您可以在类中存储一个数组,该数组与您的集合保持同步/更新。编写代码应该不会太困难。

你提到了

ReadonlyArray
。我认为你可以尝试让你的类实现这个接口,如果这是你想要的。


0
投票

这个问题之前已经回答过,但它以某种方式与此链接相关,并且可能有用。

它用于另一个对象中而不是数组中的对象的唯一 ID。

© www.soinside.com 2019 - 2024. All rights reserved.