Допустим, вы следуете правилам кодирования проекта TypeScript и используете только undefined. Ваши типы определены с необязательными свойствами, не допускающими значения NULL (например, x?: number), но данные, поступающие из API, вместо этого возвращают null.

Вы решаете написать функцию для удаления всех этих null значений из ответа, чтобы они соответствовали вашим типам:

function stripNullableProperties(obj) {
  // Return a new object without null properties
}

Как можно строго набирать такой помощник, не дублируя типы ввода и вывода? Вы можете попробовать:

function stripNullableProperties<T extends {}>(obj: T): T;

Но он не будет работать в режиме строгой проверки нуля, поскольку obj может иметь null значения, которые нельзя присвоить необязательным свойствам, не допускающим значения NULL, в T:

type A = {
  x: number;
  y?: number;
};

stripNullableProperties<A>({
  x: 1,
  y: null // Error: Type 'null' is not assignable to type 'number | undefined'.
});

Что вам действительно нужно, так это что-то вроде:

function stripNullableProperties<T extends {}>(obj: NullableOptional<T>): T;

Тип NullableOptional ‹T›

Тип NullableOptional<T> создает тип со всеми необязательными свойствами T, которые допускают значение NULL:

type A = {
  x: number;
  y?: number;
};

type B = NullableOptional<A>;
// {
//   x: number;
//   y?: number | null;
// }

Вы не найдете NullableOptional в документации TypeScript, потому что это настраиваемый тип. На самом деле это выглядит так:

type RequiredKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? never : K }[keyof T];

type OptionalKeys<T> = { [K in keyof T]-?: {} extends { [P in K]: T[K] } ? K : never }[keyof T];

type PickRequired<T> = Pick<T, RequiredKeys<T>>;

type PickOptional<T> = Pick<T, OptionalKeys<T>>;

type Nullable<T> = { [P in keyof T]: T[P] | null };

type NullableOptional<T> = PickRequired<T> & Nullable<PickOptional<T>>;

Суммируя:

  1. выберите нужные свойства из T;
  2. выберите необязательные свойства из T и сделайте их допускающими значение NULL;
  3. пересекаются (1) с (2).

С его помощью вы можете удалить все свойства, допускающие значение NULL, из объекта, интерфейс которого должен иметь только необязательные свойства, не допускающие значения NULL, при этом обеспечивая безопасность типов:

type A = {
  x: number;
  y?: number;
};

stripNullableProperties<A>({
  x: 1,
  y: null
});
// {
//   x: 1
// }: A

Вы всегда можете выполнить утверждение типа и избежать всех этих проблем, но обеспечение безопасности типов - даже в таких, казалось бы, безвредных случаях, подобных этому, - окупается в долгосрочной перспективе.

Первоначально опубликовано на rbardini.com 20 декабря 2019 г.