Как ограничить тип литерала Union одним из литеральных типов

Я хочу создать предикат типа, чтобы при использовании filter в массиве дженериков Union он возвращал правильный тип.

Я написал тип, указывающий тип ввода/вывода функции преобразования для соответствующего идентификатора:

type ConverterGroupIdToFunc = {
  text: [string, string]
  image: [File, string]
}

type ConverterGroupId = keyof ConverterGroupIdToFunc

type ConverterGroup<T extends ConverterGroupId> = {
  id: T
  convertFunc: (s: ConverterGroupIdToFunc[T][0]) => (ConverterGroupIdToFunc[T][1]);
}

И еще мне нужен массив ConverterGroup для хранения двух разных типов дженериков (ConverterGroup<"text">|ConverterGroup<"image">)[]

type ConverterGroupUnion = ConverterGroup<"text"> | ConverterGroup<"image">

Для converterGroups: (ConverterGroup<"text">|ConverterGroup<"image">)[] я бы хотел, чтобы find() возвращал тип ConverterGroup<"text">, если id группы конвертеров также равен "text".

Я пытаюсь использовать предикат типа, чтобы указать find() вернуть правильный тип, но не нашел, как:

function isConverterGroup<T extends ConverterGroupId>(converterGroupId: T) {
  return (converterGroup: ConverterGroupUnion): converterGroup is ConverterGroup<T> => {
    return converterGroup.id === converterGroupId;
  }
}

const convertFunc = converterGroups.find(isConverterGroup("text"))!.convertFunc;

Приведенный выше код дает мне ошибку:

A type predicate's type must be assignable to its parameter's type.
  Type 'ConverterGroup<T>' is not assignable to type 'ConverterGroupUnion'.
    Type 'ConverterGroup<T>' is not assignable to type 'ConverterGroup<"text">'.
      Types of property 'id' are incompatible.
        Type 'T' is not assignable to type '"text"'.ts(2677)

Вот Ссылка на игровую площадку Typescript

Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой Zod и раскрыть некоторые ее особенности, например, возможности валидации и трансформации данных, а также...
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Как заставить Remix работать с Mantine и Cloudflare Pages/Workers
Мне нравится библиотека Mantine Component , но заставить ее работать без проблем с Remix бывает непросто.
Угловой продивер
Угловой продивер
Оригинал этой статьи на турецком языке. ChatGPT используется только для перевода на английский язык.
TypeScript против JavaScript
TypeScript против JavaScript
TypeScript vs JavaScript - в чем различия и какой из них выбрать?
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Синхронизация localStorage в масштабах всего приложения с помощью пользовательского реактивного хука useLocalStorage
Не все нужно хранить на стороне сервера. Иногда все, что вам нужно, это постоянное хранилище на стороне клиента для хранения уникальных для клиента...
Что такое ленивая загрузка в Angular и как ее применять
Что такое ленивая загрузка в Angular и как ее применять
Ленивая загрузка - это техника, используемая в Angular для повышения производительности приложения путем загрузки модулей только тогда, когда они...
1
2
56
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Иногда вы знаете, что тип X можно назначить типу Y, но TypeScript не может этого увидеть, потому что либо X, либо Y зависят от некоторого общего типа. В вашем примере ConverterGroup<T> является общим, и компилятор не может определить, что его можно назначить ConverterGroupUnion. А поскольку функция защиты пользовательского типа требует, чтобы для y is X тип X был назначен typeof y, она жалуется на converterGroup is ConverterGroup<T>.

В подобных ситуациях вы обычно можете это исправить, изменив X на X & Y, Extract<X, Y> или Extract<Y, X> в зависимости от варианта использования. Пересечение всегда присваивается его членам, как и результат объединения -фильтрации Извлечь тип утилиты . Если вы правы, что X можно присвоить Y, то в конечном итоге X & Y и Extract<X, Y> (и, возможно, Extract<Y, X>, в зависимости от формы Y и X) будут просто X, как только будет указан общий тип, и результирующее поведение не изменится. Если вы ошиблись насчет назначаемости, то в итоге вы получите что-то другое, возможно никогда, поэтому вам следует быть осторожным и знать, что вы делаете с типами.

В любом случае, поскольку в вашем случае похоже, что вы просто хотите отфильтровать ConverterGroupUnion до члена, который можно назначить ConverterGroup<T>, вы можете использовать Extract<ConverterGroupUnion, ConverterGroup<T>>:

function isConverterGroup<T extends ConverterGroupId>(converterGroupId: T) {
  return (converterGroup: ConverterGroupUnion):
    converterGroup is Extract<ConverterGroupUnion, ConverterGroup<T>> => {
    return converterGroup.id === converterGroupId;
  }
}

Теперь ошибок нет, а другой код работает как положено:

const convertFunc = converterGroups.find(isConverterGroup("text"))!.convertFunc;
// const convertFunc: (s: string) => string

Обратите внимание: если вы сделали что-то странное, например

const x = isConverterGroup(Math.random() < 0.5 ? "text" : "image");
// const x: (converterGroup: ConverterGroupUnion) => converterGroup is never

Вы понимаете, что never я упомянул, потому что ConverterGroup<"text" | "image"> не связано ни с ConverterGroup<"text">, ни с ConverterGroup<"image">, поскольку ConverterGroup<T> инвариантно в T (см. Разница между дисперсией, ковариацией, контравариантностью, бивариантностью и инвариантностью в TypeScript). Что является одной из причин ошибки в первую очередь.

Детская площадка, ссылка на код

Другие вопросы по теме