Как в целом установить значения сопоставленного типа

См. следующий пример кода:

type Properties = {
  item0: { item0: string };
  item1: { item1: string };
  item2: { item2: string };
  item3: { item3: string };
  item4: { item4: string };
};

type Func<N extends keyof Properties> = ({}: Properties[N]) => number;
const Wrap = <N extends keyof Properties>(inner: Func<N>): Func<N> => {
  return (v: Properties[N]) => inner(v) + 2;
};
type FuncSet = { [Property in keyof Properties]: Func<Property> };
const WrapSet = (inner: FuncSet): FuncSet => {
  return {
    item0: Wrap(inner.item0),
    item1: Wrap(inner.item1),
    item2: Wrap(inner.item2),
    item3: Wrap(inner.item3),
    item4: Wrap(inner.item4),
  };
};

Как можно провести рефакторинг WrapSet так, чтобы он перебирал элементы в свойствах, чтобы их не приходилось указывать явно? Я ищу ответ, который не обходит систему типов машинописного текста, поскольку это вопрос машинописного текста, а не вопрос Javascript.

Вы не можете абстрагироваться от сопоставленных типов, чтобы избежать утверждений типов. Вы могли бы написать этот код, но TS безнадежно теряется, когда дело доходит до проверки правильности. Это полностью решает вопрос? Если да, то я мог бы написать ответ (существующий ответ делает то же самое, но, похоже, не касается набора текста). Если нет, то чего мне не хватает?

jcalz 17.05.2024 00:42

jcalz, если ты считаешь, что это лучший ответ, то тебе следует отправить его

Dave Butler 17.05.2024 20:05
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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
2
56
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

На самом деле вы спрашиваете, как перебирать все свойства объекта. Это можно сделать с помощью метода Object.keys(myObj), который возвращает массив всех свойств, определенных в myObj (не включая реквизиты из прототипа объекта).

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

Итак, вы можете сделать что-то вроде этого:

const WrapSet = (inner: FuncSet): FuncSet => {
  return Object.keys(inner).reduce((acc, key) => {
    acc[key] = Wrap(inner[key]);
    return acc;
  }, {});
};

В целом верно, но Typescript не очень нравится этот ответ: tsplay.dev/mb21om

Alex Wayne 16.05.2024 22:44

Да, ты прав. Может потребоваться приведение типов

Andrea Roveroni 17.05.2024 09:26
Ответ принят как подходящий

В TypeScript нет хорошего способа сделать это без использования какого-либо механизма ослабления типов, такого как утверждения типа . Язык не обладает достаточной выразительностью, необходимой для абстрагирования отображаемых типов в целом, особенно без типов более высокого рода, как это требуется в microsoft/TypeScript#1213.

я бы с удовольствием написал

const WrapSet = (inner: FuncSet): FuncSet =>
  Object.fromEntries(Object.entries(inner).map(([k, v]) => [k, Wrap<any>(v)])) as
  unknown as FuncSet;

используя Object.entries() , чтобы разделить объект на его свойства, затем сопоставить их с новыми свойствами и, наконец, использовать Object.fromEntries() , чтобы снова построить объект. Во время выполнения это работает без проблем, но я использовал утверждения типа и любой тип, чтобы избежать ошибок типа.

Если вы попытаетесь сделать этот тип более безопасным, вы обнаружите, что продолжаете сталкиваться с препятствиями, связанными с генериками высшего порядка дженериков и их ограничениями. Вы хотели бы сказать, что WrapSet — это особая форма более общего картографа mapValues():

function mapValues<T extends object>(obj: T, map: <K extends keyof T>(v: T[K]) => T[K]) {
  return Object.fromEntries(Object.entries(obj).map(([k, v]) => [k, map(v)])) as T
}

но убедить компилятор в том, что Wrap имеет правильный тип, по сути невозможно:

const WS = (inner: FuncSet) => mapValues(inner, Wrap); // error!
// -------------------------------------------> ~~~~

Правильный тип выглядит так:

const Wrap = <N extends keyof Properties>(inner: FuncSet[N]): FuncSet[N] => {
  return (v: Properties[N]) => inner(v) + 2; // error!
//~~~~~~
};

Но опять же, компилятор недоволен. Здесь просто невозможно следовать отношениям высшего порядка. Возможно, вы сможете поиграть в некоторые игры с дженериками и приблизиться к этому, но мы уже далеко прошли точку убывающей отдачи. Лично я бы придерживался версии утверждения типа до тех пор, пока в TypeScript не будут реализованы дженерики более высокого порядка (возможно, этого не произойдет никогда).

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

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