具有唯一 属性 的 TypeScript 对象集
TypeScript object set with unique property
我只写了这样的界面:
interface IProduct {
id: number;
name: string;
price: number;
description?: string;
}
现在我想 id
在 ReadonlyArray 中是唯一的。因此,当创建一些产品时,我想防止添加具有相同 ID 的对象。包含产品的数组将在文件中创建一次,并且不会被修改。
你有什么想法吗? JS Set 将是很好的解决方案,但我无法向它们添加自己的比较器。
请不要提供需要额外框架等的解决方案
你想要编译时保证吗?我怀疑这在 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 中:
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...
}
如果您需要通过索引进行随机访问,您可以在 class 中存储一个数组,该数组与集合一起保存 in-sync/updated。这应该不会太难编写代码。
您提到的是 ReadonlyArray
。我想你可以尝试让你的 class 实现这个接口,如果你想要的话。
此示例使用类型系统来静态验证是否存在重复项。
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
如果你对静态验证感兴趣,可以查看我的article
我只写了这样的界面:
interface IProduct {
id: number;
name: string;
price: number;
description?: string;
}
现在我想 id
在 ReadonlyArray 中是唯一的。因此,当创建一些产品时,我想防止添加具有相同 ID 的对象。包含产品的数组将在文件中创建一次,并且不会被修改。
你有什么想法吗? JS Set 将是很好的解决方案,但我无法向它们添加自己的比较器。 请不要提供需要额外框架等的解决方案
你想要编译时保证吗?我怀疑这在 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 中:
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...
}
如果您需要通过索引进行随机访问,您可以在 class 中存储一个数组,该数组与集合一起保存 in-sync/updated。这应该不会太难编写代码。
您提到的是 ReadonlyArray
。我想你可以尝试让你的 class 实现这个接口,如果你想要的话。
此示例使用类型系统来静态验证是否存在重复项。
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
如果你对静态验证感兴趣,可以查看我的article