Инвертировать ключ типа T

Использование машинописного текста

как я могу сделать параметр «значение» в функции «formatValue» типом keyOf T?

export interface ICol<T extends Record<string, any>, K extends keyof T = keyof T> {
    accessor?: K;
    formatValue?: (value: T[K]) => JSX.Element | string | number;
}
const col: ICol<{ a: number; b: string; c: Date; }> = {
    accessor: 'a',
    formatValue: (value) => {
        return `Value is ${value}`;
    },
};

Во всплывающей подсказке должно быть указано value:number, когда метод доступа равен «a», но value:string, когда метод доступа равен «b». Текущая подсказка гласит: (parameter) value: string | number | date

В этом случае мне нужно, чтобы значение было числом.

Спасибо.

Почему formatValue является необязательной функцией-членом? Это не самый удобный дизайн API...

Dai 23.08.2024 03:09

потому что, если нет formatValue.. я просто напечатаю значение как есть!

Fraga 23.08.2024 04:20

Внесите изменения, чтобы включить информацию о всплывающей подсказке в виде обычного текста. • Является ли этот подход тем, что вы ищете? Если да, то я напишу ответ или найду подходящий дубликат. Если нет, то что мне не хватает?

jcalz 23.08.2024 04:31

ух ты! решение работает!.. я пытаюсь это понять! это потрясающий человек! УХ ТЫ! пожалуйста, напишите ответ, чтобы одобрить его.

Fraga 23.08.2024 21:26

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

Fraga 23.08.2024 21:29

Нет, вы не можете использовать interface здесь, потому что мы фактически определяем тип объединения. Итак, это будет type MyUnion = 'foo' | 'bar', который вы не можете определить с помощью interface.

Nickofthyme 23.08.2024 21:52
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
2
6
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

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

export type $Values<T extends object> = T[keyof T];

export type ICol<T extends Record<string, any>> = $Values<{
  [K in keyof T]: {
    accessor?: K;
    formatValue?: (value: T[K]) => JSX.Element | string | number;
  }
}>

const colA: ICol<{ a: number; b: string; c: Date; }> = {
  accessor: 'a',
  formatValue: (value) => {
    return `Value is ${value.toFixed()}`; // value is a number
  },
};

const colB: ICol<{ a: number; b: string; c: Date; }> = {
  accessor: 'b',
  formatValue: (value) => {
    return `Value is ${value.charCodeAt(1)}`; // value is a string
  },
};

const colC: ICol<{ a: number; b: string; c: Date; }> = {
  accessor: 'c',
  formatValue: (value) => {
    return `Value is ${value.getDate()}`; // value is a Date
  },
};

Ссылка на TS Playground

Так что же это делает? Идея состоит в том, что мы хотим получить результирующий тип, представляющий собой объединение всех возможных вариантов, который будет выглядеть примерно так...

type FinalType =
  | {
      accessor?: 'a';
      formatValue?: ((value: number) => JSX.Element | string | number) | undefined;
    }
  | {
      accessor?: 'b';
      formatValue?: ((value: string) => JSX.Element | string | number) | undefined;
    }
  | {
      accessor?: 'c';
      formatValue?: ((value: Date) => JSX.Element | string | number) | undefined;
    };

Для этого мы перебираем все ключи объекта T, используя синтаксис [K in keyof T]. Это даст нам тип, который выглядит так...

type LoopedObjectTypes = {
  a: {
    accessor?: 'a';
    formatValue?: ((value: number) => JSX.Element | string | number) | undefined;
  };
  b: {
    accessor?: 'b';
    formatValue?: ((value: string) => JSX.Element | string | number) | undefined;
  };
  b: {
    accessor?: 'c';
    formatValue?: ((value: Date) => JSX.Element | string | number) | undefined;
  };
}

Затем мы просто переносим значения по объекту, используя вспомогательный тип $Values, и получаем окончательный тип.

Одна вещь, которую я настоятельно рекомендую при попытке понять такой сложный тип, — это обратиться к этому репозиторию Utility-Types, который содержит множество полезных типов для использования в вашем коде, а также позволяет увидеть, как эти типы на самом деле создаются.

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

Вы хотите, чтобы ICol<{ a: number; b: string; c: Date; }> без второго аргумента общего типа K был эквивалентен объединению ICol<{ a: number; b: string; c: Date; }, K> для каждого K в keyof T. То есть вы хотите распределить объединение в K по операции ICol<T>. Есть два относительно простых способа сделать это.


Один из них — использовать распределительный условный тип , например:

type ICol<T extends Record<string, any>, K extends keyof T = keyof T> =
  K extends unknown ? {
    accessor?: K;
    formatValue?: (value: T[K]) => JSX.Element | string | number;
  } : never

Поскольку K является параметром универсального типа, условный тип K extends unknown ? ⋯ : never автоматически распределяет по объединениям в K. На самом деле мы не пытаемся ничего проверять (K extends unknown всегда верно), просто используем дистрибутивный условный тип. Это приводит к:

type Z = ICol<{ a: number; b: string; c: Date; }>;
/* type Z = {
    accessor?: "a" | undefined;
    formatValue?: ((value: number) => JSX.Element | string | number) | undefined;
} | {
    accessor?: "b" | undefined;
    formatValue?: ((value: string) => JSX.Element | string | number) | undefined;
} | {
    accessor?: "c" | undefined;
    formatValue?: ((value: Date) => JSX.Element | string | number) | undefined;
} */

И тогда вы получаете ожидаемое поведение, где value выводится как number:

const col: ICol<{ a: number; b: string; c: Date; }> = {
  accessor: 'a',
  formatValue: (value) => {
    return `Value is ${value}`;
  },
};

Другой способ — использовать тип распространяемого объекта, как это описано в microsoft/TypeScript#47109 , где вы создаете сопоставленный тип над членами объединения K, а затем немедленно индексируете в этот сопоставленный тип с помощью K:

type ICol<T extends Record<string, any>, K extends keyof T = keyof T> = { [P in K]: {
  accessor?: P;
  formatValue?: (value: T[P]) => JSX.Element | string | number;
} }[K]

Здесь мы объявляем параметр типа P для перебора элементов K. В результате создается сопоставленный тип с теми же ключами, что и T, но свойства которого являются типами объектов, которые мы ищем. Когда мы индексируем этот сопоставленный тип с помощью K, мы получаем объединение этих типов объектов.

Таким образом, происходит тот же вывод:

type Z = ICol<{ a: number; b: string; c: Date; }>;
/* type Z = {
    accessor?: "a" | undefined;
    formatValue?: ((value: number) => JSX.Element | string | number) | undefined;
} | {
    accessor?: "b" | undefined;
    formatValue?: ((value: string) => JSX.Element | string | number) | undefined;
} | {
    accessor?: "c" | undefined;
    formatValue?: ((value: Date) => JSX.Element | string | number) | undefined;
} */

Эти типы по-прежнему позволяют вам передать тип для K, если вы не хотите использовать по умолчаниюkeyof T, поэтому вы можете получить более конкретный тип, если хотите:

type X = ICol<{ a: number; b: string; c: Date; }, "a">;
/* type X = {
  accessor?: "a" | undefined;
  formatValue?: ((value: number) => JSX.Element | string | number) | undefined;
} */

Если вам не нужна эта возможность, вы можете переписать ICol<T>, чтобы удалить второй параметр типа:

type ICol<T extends Record<string, any>> =
  keyof T extends infer K ? K extends keyof T ? {
    accessor?: K;
    formatValue?: (value: T[K]) => JSX.Element | string | number;
  } : never : never;

или

type ICol<T extends Record<string, any>> = { [P in keyof T]: {
  accessor?: P;
  formatValue?: (value: T[P]) => JSX.Element | string | number;
} }[keyof T]

Обратите внимание: поскольку ICol<T> хотя бы иногда должен быть типом объединения, вы не можете создать ICol<T> интерфейс 🔁 . Интерфейсы могут представлять только отдельные типы объектов (или пересечения типов объектов) со статически известными членами.

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

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

Машинописный текст говорит, что собственность не существует
Как заставить TypeScript возвращать другое значение в зависимости от необязательного аргумента
Использование дженериков для сопоставления одного строкового литерала в Typescript
Что означает `T расширяет только чтение неизвестно [] | []` в сигнатуре функции TypeScript `Promise.all`?
Репликация агрегации проекта mongodb в Typescript
Могу ли я сделать вывод intellisense более читабельным для универсального Typescript с помощью Omit?
Как создать универсальный крючок для извлечения данных в TypeScript для API с различными структурами ответов?
Проблема вывода типа с массивом универсальных объектов конфигурации с разными внутренними типами, которые зависят от другого внутреннего свойства
Как обеспечить соблюдение типа универсального компонента Vue для нескольких свойств
Требовать, чтобы абстрактный класс Typescript определял свойства из другого интерфейса, которые должны быть определены в родительском классе