Типизированный интерфейс общего значения ключа в машинописном тексте

У меня есть следующий пример объекта:

let foo: Foo = {
  'key1': { default: 'foo', fn: (val:string) => val },
  'key2': { default: 42, fn: (val:number) => val },

  // this should throw an error, because type of default and fn don't match
  'key3': { default: true, fn: (val:string) => val }
}

Интерфейс должен выглядеть примерно так:

interface Foo {
  [key: string]: { default: T, fn: (val:T) => any }
}

Это, конечно, не работает, потому что T не определен.

Итак, я подумал об этом:

interface FooValue<T> {
  default: T;
  fn: (val:T) => any;
}

interface Foo {
  [key: string]: FooValue<?>
}

Но и там я застрял. Потому что я не могу определить общий тип FooValue.

Если я использую FooValue<any>, то, конечно, все набирается как any. Хотя это не работает.

Я хочу убедиться, что тип default и тип параметра fn всегда совпадают.

Есть какое-нибудь решение? Или этого нельзя сделать?

что насчет interface Foo<T>?

Explosion Pills 29.03.2018 20:14

Затем я должен определить T при выполнении let foo: Foo<???> = {...}. Затем я бы установил общий тип для каждого ключа. Таким образом, все объекты FooValue должны быть одного типа. Но, как вы можете видеть в примере объекта: мне нужен другой общий тип для каждой пары "ключ-значение".

Benjamin M 29.03.2018 20:17
Зод: сила проверки и преобразования данных
Зод: сила проверки и преобразования данных
Сегодня я хочу познакомить вас с библиотекой 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 для повышения производительности приложения путем загрузки модулей только тогда, когда они...
16
2
17 128
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Как насчет определения Foo<T> как сопоставленный тип, например:

interface FooValue<T> {
  default: T;
  fn: (val: T) => any;
}

type Foo<T> = {
  [K in keyof T]: FooValue<T[K]>
}

В этом случае, если T - это некоторый нормальный тип объекта, такой как {a: string, b: number, c: boolean}, то Foo<T> - это его версия в формате Foo: {a: FooValue<string>, b: FooValue<number>, c: FooValue<boolean>}. Теперь вы можете создать вспомогательную функцию, которая принимает литерал объекта, только если он может быть выведен как Foo<T> для некоторого типа T:

function asFoo<T>(foo: Foo<T>): Foo<T> {
  return foo;
}

Эта функция работает, потому что компилятор TypeScript может выполнять вывод из сопоставленных типов, позволяя ему выводить T из Foo<T>. Вот это работает:

let foo = asFoo({
  key1: { default: 'foo', fn: (val: string) => val },
  key2: { default: 42, fn: (val: number) => val }
});
// inferred as { key1: FooValue<string>; key2: FooValue<number>;}

А вот и не получается:

let badFoo = asFoo(
  key1: { default: 'foo', fn: (val: string) => val },
  key2: { default: 42, fn: (val: number) => val },
  key3: { default: true, fn: (val: string) => val }
}); 
// error! Types of property 'key3' are incompatible. 
// Type 'boolean' is not assignable to type 'string'.

Надеюсь, это поможет. Удачи!


Обновление: в приведенном выше коде предполагается, что вы согласны с выводом foo.key1.fn('abc') как типа any, поскольку FooValue<string>['fn'] определен как функция, возвращающая any. Это как бы забывает тип вывода из исходного литерала объекта. Если вы хотите, чтобы foo передавал помнить тип возвращаемого значения методов fn его свойств, вы можете сделать эту немного другую вспомогательную функцию:

function asFoo<T, F>(foo: F & Foo<T>): F {
  return foo;
}

let foo = asFoo({
  key1: { default: 'foo', fn: (val: string) => val },
  key2: { default: 42, fn: (val: number) => val },
  // next line would cause error
  // key3: { default: true, fn: (val: string)=>val} 
})

const key1fnOut = foo.key1.fn('s') // known to be string
const key2fnOut = foo.key2.fn(123) // known to be number

И это работает. В этом случае asFoo() просто проверяет, что вход является Foo<T> для некоторого T, но не приводит тип вывода к Foo<T>. В зависимости от ваших вариантов использования вы можете предпочесть это решение другому. И снова удачи.

Спасибо! Замечательная помощь! Думаю, мне нужна была вспомогательная функция.

Benjamin M 30.03.2018 17:32

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