Typescript 中的类型化通用键值接口
Typed Generic Key Value Interface in Typescript
我有以下示例对象:
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
的参数类型始终相同
有什么解决办法吗?还是不能这样做?
如何将 Foo<T>
定义为 mapped type,如下所示:
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>
}。现在你可以创建一个辅助函数,它只接受一个对象文字,如果它可以被推断为某种类型 T
:
的 Foo<T>
function asFoo<T>(foo: Foo<T>): Foo<T> {
return foo;
}
此函数有效,因为 TypeScript 编译器可以执行 inference from mapped types,允许它从 Foo<T>
推断出 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']
被定义为 returns any
。它有点忘记了原始对象文字的输出类型。如果你想让foo
记住其属性fn
方法的return类型,你可以做这个稍微不同的辅助函数:
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()
只是验证输入是某些 T
的 Foo<T>
,但它不会将输出类型强制转换为 Foo<T>
。根据您的用例,您可能更喜欢此解决方案而不是其他解决方案。再次祝你好运。
我有以下示例对象:
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
的参数类型始终相同
有什么解决办法吗?还是不能这样做?
如何将 Foo<T>
定义为 mapped type,如下所示:
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>
}。现在你可以创建一个辅助函数,它只接受一个对象文字,如果它可以被推断为某种类型 T
:
Foo<T>
function asFoo<T>(foo: Foo<T>): Foo<T> {
return foo;
}
此函数有效,因为 TypeScript 编译器可以执行 inference from mapped types,允许它从 Foo<T>
推断出 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']
被定义为 returns any
。它有点忘记了原始对象文字的输出类型。如果你想让foo
记住其属性fn
方法的return类型,你可以做这个稍微不同的辅助函数:
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()
只是验证输入是某些 T
的 Foo<T>
,但它不会将输出类型强制转换为 Foo<T>
。根据您的用例,您可能更喜欢此解决方案而不是其他解决方案。再次祝你好运。