javascript - TypeScript-objektsett med unik egenskap
jeg skrev enkle grensesnitt slik:
interface IProduct {
id: number;
name: string;
price: number;
description?: string;
}
Nå vil jeg at id skal være unik i ReadonlyArray. Så når jeg lager noen produkter, vil jeg forhindre å legge til objekter med samme id. Array med produkter vil bli opprettet én gang, i filen, og vil ikke bli endret.
Har du noen idé for det? JS Set vil være en god løsning, men jeg kan ikke legge til egen sammenligner til dem. Vennligst ikke gi løsning som krever ekstra rammeverk osv.
Svar #1
Dette eksemplet bruker typesystemet for å statisk validere om det finnes duplikater eller ikke.
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'))
Lekeplass
Validering - itererer rekursivt gjennom alle produkter som sendes inn i funksjonen. Hvis product[id] allerede eksisterer i akkumulatoren - erstatt id-egenskapen med never, ellers legg bare til produktet i akkumulatoren.
Vennligst se kommentarene #1, #2 ....
Hvis du ikke vil bruke rest-operatoren, vurder dette eksempelet:
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
Lekeplass
Hvis du er interessert i statisk validering, kan du sjekke artikkelen min
Svar #2
Vil du ha kompileringstidsgarantier for dette? Jeg tviler på at dette ville være mulig i TypeScript. Redigering: Kanskje det er mulig etter å ha sett det andre svaret.
Imidlertid er JavaScript-koden ganske enkel:
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);
}
Du kan pakke denne koden inn i din egen klasse med et sett av funksjoner:
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...
}
Hvis du trenger tilfeldig tilgang via indeks, kan du lagre en array i klassen din som holdes synkronisert/oppdatert sammen med settet ditt. Dette burde ikke være for vanskelig å kode.
Du nevnte ReadonlyArray. Jeg tror du kan prøve å få klassen din til å implementere dette grensesnittet, hvis det er noe du ønsker.
