打字稿泛型保留类型

时间:2020-09-04 05:29:12

标签: typescript generics

我有两种类型定义为:

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.abcfoo.def必须为numberfoo.xyz需要为string(这不好当三个都为number | string时。

1 个答案:

答案 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>

Playground Link

但这使用起来很烦人。


另一个选择是显式地通用键入整个数组,以便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>

Playground Link

但是由于某种原因,您会看到它弄乱了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


也可以使用非常复杂的类型来执行此操作,以将每个键和值作为类型来获取,但不太确定... Playground Link