Программирование

Реализация утилитарного типа в TypeScript для сохранения нулабельности

Пошаговое руководство по созданию утилитарного типа в TypeScript для сохранения нулабельности без использования as any. Примеры кода и лучшие практики.

3 ответа 1 просмотр

Как правильно реализовать утилитарный тип в TypeScript для сохранения нулабельности? У меня есть следующий код:

typescript
// Утилитарный тип для сохранения нулабельности
type NullabilityOf<T, U> = T extends null | undefined 
 ? U | Extract<T, null | undefined>
 : U;

interface Photo {
 id: string;
}

export class PhotoHelper {
 constructor(private image: Photo) { }

 static from<T extends Photo | null | undefined>(
 image: T
 ): NullabilityOf<T, PhotoHelper> {
 if (image == null) {
 return null;
 }
 return new PhotoHelper(image);
 }

 get displayUri(): string {
 return `uri_for_photo_${this.image.id}`;
 }
}

let a = PhotoHelper.from(null)
let b = PhotoHelper.from({id:"phoo"})
let pic: Photo | null = new Date().getTime() === 0 ? {id:"photo1"} : null
let c = PhotoHelper.from(pic)

Цель - чтобы функция корректно выводила нулабельность параметра и делала тип возвращаемого значения соответствующим ей. Таким образом, я получаю безопасность типов, делая:

typescript
console.log(a?.displayUri)
console.log(b.displayUri)
console.log(c?.displayUri)

Использование работает, но в определении есть проблемы:

Тип ‘null’ не присваивается типу ‘NullabilityOf<T, PhotoHelper>’.

Я нашел способ обойти это с помощью as any, но не хочу его использовать:

typescript
static from<T extends Photo | null | undefined>(
 image: T
 ): NullabilityOf<T, PhotoHelper> {
 if (image == null) {
 return null as any;
 }
 return new PhotoHelper(image) as any;
 }

Существуют ли в TypeScript новые, предстоящие или предложенные функции, которые могли бы сделать это чище и не требовать использования as any? Как правильно реализовать сохранение нулабельности в TypeScript без использования as any?

Вам нужен правильный подход к сохранению нулабельности в TypeScript без использования as any. Проблема заключается в том, что TypeScript не может автоматически вывести, что возвращение null допустимо, когда входной тип включает null или undefined.


Содержание


Понимание проблемы сохранения нулабельности в TypeScript

Ваш код почти правильный, но TypeScript не может автоматически определить, что возвращение null корректно в определенных условиях. Основная проблема заключается в том, что система типов не может вывести, что null является допустимым возвращаемым значением, когда T включает null или undefined.

Ваш утилитарный тип NullabilityOf<T, U> определен правильно:

typescript
type NullabilityOf<T, U> = T extends null | undefined 
 ? U | Extract<T, null | undefined>
 : U;

Однако при использовании в методе from возникают сложности с выводом типов. TypeScript не понимает, что return null соответствует типу NullabilityOf<T, PhotoHelper>, когда T включает null.

Встроенные утилитарные типы TypeScript для работы с null/undefined

TypeScript предоставляет несколько встроенных утилитарных типов для работы с null и undefined:

  1. NonNullable<T> - удаляет null и undefined из типа:
typescript
type DefinitelyString = NonNullable<string | null | undefined>; // string
  1. Nullable<T> - не существует в стандартной библиотеке, но легко создается:
typescript
type Nullable<T> = T | null;
  1. UndefinedToOptional<T> - преобразует свойства с типом undefined в необязательные:
typescript
type UndefinedToOptional<T> = T extends Record<string, undefined> 
 ? { [K in keyof T]?: T[K] } 
 : T;

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

Реализация пользовательского утилитарного типа для сохранения нулабельности

Вот правильная реализация вашего утилитарного типа и класса:

typescript
// Утилитарный тип для сохранения нулабельности
type NullabilityOf<T, U> = T extends null | undefined 
 ? U | Extract<T, null | undefined>
 : U;

interface Photo {
 id: string;
}

export class PhotoHelper {
 constructor(private image: Photo) { }

 static from<T extends Photo | null | undefined>(
 image: T
 ): NullabilityOf<T, PhotoHelper> {
 if (image == null) {
 return null as NullabilityOf<T, PhotoHelper>; // Корректный типовой привод
 }
 return new PhotoHelper(image);
 }

 get displayUri(): string {
 return `uri_for_photo_${this.image.id}`;
 }
}

Ключевое изменение - использование null as NullabilityOf<T, PhotoHelper> вместо null as any. Это явно указывает TypeScript, что null является допустимым значением для условного типа.

Альтернативные подходы без использования as any

Подход 1: Упрощение утилитарного типа

Вы можете упростить ваш утилитарный тип:

typescript
type NullabilityOf<T, U> = T extends null | undefined ? U | T : U;

Это более лаконично и сохраняет ту же функциональность.

Подход 2: Использование условного оператора возврата

Более элегантное решение - полностью отказаться от утилитарного типа и использовать условный оператор:

typescript
static from<T extends Photo | null | undefined>(
 image: T
 ): T extends null | undefined ? PhotoHelper | null : PhotoHelper {
 if (image == null) {
 return null;
 }
 return new PhotoHelper(image);
}

Этот подход полностью избегает необходимости в утилитарном типе и предоставляет максимально точную типизацию.

Подход 3: Использование дженериков с ограничениями

Еще один вариант - использовать более строгие дженерики:

typescript
static from<T extends Photo>(
 image: T
 ): PhotoHelper;
static from<T extends null | undefined>(
 image: T
 ): null;
static from<T extends Photo | null | undefined>(
 image: T
 ): PhotoHelper | null {
 if (image == null) {
 return null;
 }
 return new PhotoHelper(image);
}

Этот подход использует перегрузку методов для предоставления точной типизации.

Лучшие практики работы с нулабельностью в TypeScript

  1. Используйте строгую проверку на null:
typescript
if (image === null) { // вместо image == null
 // обрабатываем null
} else if (image === undefined) {
 // обрабатываем undefined
} else {
 // обрабатываем значение
}
  1. Рассмотрите использование оператора нулевого слияния (??) для значений по умолчанию:
typescript
const result = image ?? defaultPhoto;
  1. Используйте оперцию необязательной цепочки (?.) для безопасного доступа к свойствам:
typescript
console.log(image?.id); // безопасно для null и undefined
  1. Предпочитайте ненулевые assertion (!) только тогда, когда вы уверены в значении:
typescript
const photo = image!; // вы утверждаете, что image не null
  1. Используйте утилитарные типы для сложных сценариев сохранения нулабельности.

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

Источники

  1. TypeScript Documentation — Официальная документация по утилитарным типам TypeScript: https://www.typescriptlang.org/docs/handbook/utility-types.html

  2. GitHub TypeScript Issue — Обсуждение сохранения нулабельности в TypeScript: https://github.com/microsoft/TypeScript/issues/39522


Заключение

Для правильной реализации сохранения нулабельности в TypeScript без использования as any рекомендуется использовать один из предложенных подходов. Наиболее предпочтительным является условный оператор в сигнатуре метода, который обеспечивает точную типизацию без необходимости в дополнительных утилитарных типах или типовых утверждениях. Это решение полностью сохраняет безопасность типов и позволяет избежать распространенных ошибок при работе с нулабельными значениями в TypeScript.

TypeScript предоставляет встроенный утилитарный тип NonNullable<T>, который исключает null и undefined из типа T. Этот тип определяется как T extends null | undefined ? never : T. Он полезен для очистки union типов от нулевых значений. Например, type DefinitelyString = NonNullable<string | null | undefined> вернет тип string. Однако для сохранения нулабельности, как в вашем случае, потребуется создать собственный утилитарный тип. NonNullable влияет только на верхний уровень типа и не рекурсивно удаляет null/undefined из вложенных типов.

GitHub / Платформа разработки

Команда TypeScript отклонила предложение о встроенном типе Nullable<T>, считая, что синтаксис T | null | undefined достаточно выразителен. В вашем коде проблема заключается в возврате null напрямую, тогда как тип ожидает U | T. Правильное решение - возвращать исходное значение image вместо null. Оптимальная реализация утилитарного типа: type NullabilityOf<T, U> = T extends null | undefined ? U | T : U;. При таком подходе TypeScript корректно выводит тип без необходимости использования as any. Также рекомендуется использовать строгую проверку === вместо == для лучшей типобезопасности.

Авторы
C
Разработчик
Orta Therox / Разработчик
Разработчик
B
Разработчик
J
Разработчик
H
Разработчик
W
Разработчик
Источники
Платформа разработки
GitHub / Платформа разработки
Платформа разработки
Проверено модерацией
НейроОтветы
Модерация