[英]TypeScript object set with unique property
我簡單地寫了這樣的接口:
interface IProduct {
id: number;
name: string;
price: number;
description?: string;
}
現在我想id
在 ReadonlyArray 中是唯一的。 因此,當創建一些產品時,我想防止添加具有相同 id 的對象。 包含產品的數組將在文件中創建一次,不會被修改。
你有什么想法嗎? JS Set 將是很好的解決方案,但我無法向它們添加自己的比較器。 請不要提供需要額外框架等的解決方案。
此示例使用類型系統靜態驗證是否存在重復項。
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]
- 用never
替換id
屬性,否則只需將 product 添加到累加器。
請參閱評論#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
如果您對靜態驗證感興趣,可以查看我的文章
你想要編譯時保證嗎? 我懷疑這在 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
。 我認為你可以嘗試讓你的類實現這個接口,如果這是你想要的。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.