typescript - Cum să impui ca un tip să nu fie gol
Presupun că am un tip în care toate proprietățile sunt opționale:
type T = {
x?: number;
y?: string;
z?: number;
...
}
Există o modalitate de a construi un tip care să permită toate valorile de tip T, dar să solicite cel puțin o proprietate? De exemplu:
let a: NotEmpty<T> = { z: 1 }; // ok
a = {}; // error
Desigur, o modalitate de a face acest lucru ar fi o uniune a tuturor combinațiilor cu cel puțin o proprietate necesară, dar acest lucru devine oarecum incomod atunci când numărul de proprietăți este mare. Există o modalitate mai generală?
Răspuns #1
Uniunea despre care vorbești, de „toate combinațiile cu cel puțin o proprietate necesară”, nu este prea dificil de scris, mai ales că poți să scrii tipul utilitar NotEmpty<T> pentru a o calcula automat. Și acel tip are doar un membru de uniune pe fiecare cheie de proprietate, deci se scalează bine cu numărul de proprietăți (nu este o explozie factorială sau combinatorică sau ceva de genul acesta).
Iată o modalitate de a-l scrie:
type NotEmpty<T> = T &
{ [K in keyof T]-?: { [P in K]-?: T[K] } }[keyof T];
Deci un NotEmpty<T> este T intersectat cu ceva, astfel încât știm că, cel puțin, NotEmpty<T> este un subtip de T. Lucrul cu care îl intersectăm,
{ [K in keyof T]-?: { [P in K]-?: T[K] } }[keyof T]
este un tip de obiect distributiv (așa cum a fost denumit în microsoft/TypeScript#47109), un tip mapat în care indexăm imediat cu toate cheile sale pentru a obține o uniune. În exemplul de mai sus, obținem uniunea {[P in K]-?: T[K]} pentru fiecare K din keyof T.
Și acel tip {[P in K]-?: T[K]} este echivalent cu Required<Pick<T, K>> (vezi tipurile de utilitate Required și Pick): un obiect care are cu siguranță o proprietate K definită din T.
Deci, întregul lucru este atunci: NotEmpty<T> este un T care este, de asemenea, cunoscut că are unele proprietăți definite din proprietățile din T.
Să îl testăm pe exemplul tău:
type NotEmptyT = NotEmpty<T>
/* type NotEmptyT = T & ({ x: number; } | { y: string; } | { z: number; }) */
Arată bine, să ne asigurăm că se comportă așa cum ne dorim:
let a: NotEmpty<T>;
a = { z: 1 }; // ok
a = {}; // error
Arată bine și!
Link de teren de joacă către cod
