我有两种类型定义为:
type Foo<N extends string, T extends any> = { [name in N]: T };
type Bar<N extends string, T extends any> = { name: N; obj: T };
我有一个定义为的函数:
function makeFooFromBar<N extends string, T extends any>(params: Array<Bar<N, T>>): Foo<N, T> {
const ret: any = {};
for (const { name, obj } of params) {
ret[name] = obj;
}
return ret;
}
这应该做的是:
const foo = makeFooFromBar([
{ name: 'abc', obj: 10 },
{ name: 'def', obj: 20 },
]);
// has value 10
foo.abc;
// has value 20
foo.def;
到目前为止,这很好,但仅在obj
始终为相同类型时才有效。例如,这不起作用:
const foo = makeFooFromBar([
{ name: 'abc', obj: 10 },
{ name: 'def', obj: 20 },
{ name: 'xyz', obj: 'abc' }, // error, string can not be assigned to number
]);
有没有办法使这项工作有效?另外,我还需要保留原始类型,因此foo.abc
和foo.def
必须为number
,foo.xyz
需要为string
(这不好当三个都为number | string
时。
答案 0 :(得分:0)
您可以通过显式提供泛型类型来进行编译:
const foo2 = makeFooFromBar<'abc' | 'def' | 'xyz', 10 | 20 | 'abc'>([
{ name: 'abc', obj: 10 },
{ name: 'def', obj: 20 },
{ name: 'xyz', obj: 'abc' },
]);
// const foo2: Foo<"abc" | "def" | "xyz", "abc" | 10 | 20>
但这使用起来很烦人。
另一个选择是显式地通用键入整个数组,以便TypeScript不会过早推断类型:
function makeFooFromBar<N extends string, T, A extends Array<Bar<N, T>>>(params: A): Foo<N, T> {
...
}
const foo2 = makeFooFromBar([
{ name: 'abc', obj: 10 },
{ name: 'def', obj: 20 },
{ name: 'xyz', obj: 'abc' },
]);
// const foo2: Foo<string, unknown>
但是由于某种原因,您会看到它弄乱了T
的类型。
我们可以将推论移至返回类型,这似乎有所帮助:
function makeFooFromBar<A extends Array<Bar<any, any>>>(params: A): A extends Array<Bar<infer N, infer T>> ? Foo<N, T> : never {
...
}
const foo2 = makeFooFromBar([
{ name: 'abc', obj: 10 },
{ name: 'def', obj: 20 },
{ name: 'xyz', obj: 'abc' },
]);
// const foo2: Foo<string, string | number>
也可以使用非常复杂的类型来执行此操作,以将每个键和值作为类型来获取,但不太确定... Playground Link