У меня есть следующий пример объекта:
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
всегда совпадают.
Есть какое-нибудь решение? Или этого нельзя сделать?
Затем я должен определить T
при выполнении let foo: Foo<???> = {...}
. Затем я бы установил общий тип для каждого ключа. Таким образом, все объекты FooValue
должны быть одного типа. Но, как вы можете видеть в примере объекта: мне нужен другой общий тип для каждой пары "ключ-значение".
Как насчет определения 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>
. В зависимости от ваших вариантов использования вы можете предпочесть это решение другому. И снова удачи.
Спасибо! Замечательная помощь! Думаю, мне нужна была вспомогательная функция.
что насчет
interface Foo<T>
?