Tip generic TypeScript cu nume de chei modificate dintr-un alt tip
Să zicem că am o interfață
interface Foo {
id: string;
bar: string;
}
interface Person {
name: string;
age: number;
birthdate: Date;
active: boolean;
foo: Foo;
}
Vreau să pot crea o interfață cu numele primei interfețe, dar concatenate cu un alt șir. Deci, dat fiind tipul Generic FilterFor, pot să creez un tip generic
type FilterFor<T> = ...
Astfel încât apelarea FilterFor<Person> ar produce următoarea interfață:
interface FilterForPerson {
name: string;
name__in: string[];
name__search: string;
age: number;
age__gt: number;
age__gte: number;
age__lt: number;
age__lte: number;
birthdate: Date;
birthdate__gt: Date;
birthdate__gte: Date;
birthdate__lt: Date;
birthdate__lte: Date;
foo: string|Foo
foo__in: Array<string|Foo>
active: boolean
}
editare: Am adăugat câmpurile date, foo, active
Răspuns #1
Există diverse moduri posibile de a implementa acest lucru. Soluția pe care o prezint aici implică remaparea cheilor în tipuri mapate, ceea ce vă permite să transformați atât cheile, cât și valorile tipului dvs. de intrare. Mai întâi vom calcula un tip intermediar numit Fields<T> care transformă tipul dvs. de intrare T într-o mare uniune de tipuri tuple cheie-valoare, iar apoi vom crea FilterFor<T> peste rezultatele obținute pentru a obține tipul final. (Mulțumiri lui @Oblosys pentru această abordare).
Deci Fields<{a: string, b: number}> ar fi ceva de genul ["a", string] | ["a__search", string] | ["a__in", string[]] | ["b", number] | ["b__gt", number] | ⋯, iar apoi FilterFor<{a: string, b: number}> ar parcurge Fields<{a: string, b: number}> pentru a obține {a: string, a__search: string, a__in: string[], b: number, b__gt: number, ⋯ }.
Iată cum aș scrie Fields<T>:
type Fields<T> = { [K in string & keyof T]:
T[K] extends number | Date ? [K | `${K}__${"gt" | "gte" | "lt" | "lte"}`, T[K]] :
T[K] extends object ? [K, string | T[K]] | [`${K}__in`, (string | T[K])[]] :
T[K] extends string ? [K | `${K}__search`, string] | [`${K}__in`, string[]] :
[K, T[K]]
}[string & keyof T]
Acesta este un tip de obiect distributiv de forma {[K in KK]: F<K>}[KK], care ajunge să calculeze o reuniune de F<K> pentru fiecare K din reuniunea KK. În exemplul de mai sus, KK este string & keyof T, sau doar cheile de tip string ale tipului de intrare T (aceasta exclude cheile de tip number și cheile de tip symbol, dacă există). Și F<K> este o serie de tipuri condiționale care creează tuplurile corecte cheie-valoare pentru K.
Regulile particulare pe care le urmez sunt... mai întâi verifică tipul valorii T[K] al proprietății lui T la cheia K.
Dacă este
numbersauDate, atunci vrem un set de chei care includeKși o concatenare de tip șablon-literal aKcu diferitele sufixe de comparație, iar tipul valorii rămâne neschimbat.Altfel, dacă este un
obiect(și rețineți căDateeste unobiect, deci trebuie să procedăm în această ordine), atunci dorim să păstrăm cheiaKși valoareaT[K]unite custring, și de asemenea dorim cheiaKcu sufixul__inși o valoare care să fie un array deT[K]-unit-cu-string.Altfel, dacă este
string, atunci dorim caKșiK-cu-sufix-__searchsă aibă o valoarestring, iarK-cu-sufix-__insă aibă o valoarestring[].Altfel, pur și simplu lăsați cheia
Kși valoareaT[K]așa cum sunt.
Odată ce facem asta, FilterFor<T> este pur și simplu:
type FilterFor<T> = { [F in Fields<T> as F[0]]: F[1] }
care pur și simplu parcurge fiecare intrare în Field<T> folosind primul element ca cheie și al doilea element ca valoare.
Bine, să îl testăm:
interface Foo {
id: string;
bar: string;
}
interface Person {
name: string;
age: number;
birthdate: Date;
active: boolean;
foo: Foo;
}
type FilterForPerson = FilterFor<Person>
/* type FilterForPerson = {
name: string;
name__search: string;
name__in: string[];
age: number;
age__gt: number;
age__gte: number;
age__lt: number;
age__lte: number;
birthdate: Date;
birthdate__gt: Date;
birthdate__gte: Date;
birthdate__lt: Date;
birthdate__lte: Date;
active: boolean;
foo: string | Foo;
foo__in: (string | Foo)[];
} */
Arată bine! Proprietățile age și birthdate primesc tratamentul number-sau-Date, în timp ce proprietatea foo primește tratamentul non-Date-object, proprietatea name primește tratamentul string, iar active primește tratamentul pentru tot ce rămâne.
Link de teren de joacă către cod
