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