假设我们的类型为T
:
type T = {
type: string,
}
和一个接受T
数组并返回其键为每个T.type
的值且值为T的对象的函数。
const toMap = (...args: T[]) => args.reduce((res, t) => ({
...res,
[t.type]: t
}), {});
因此,对于此给定的示例:
const a = { type: 'hello' };
const b = { type: 'world' };
const c = { type: 'foo' };
const map = toMap(a, b, c);
我希望这个结果
{
hello: { type: 'hello' },
world: { type: 'world' },
foo: { type: 'foo' },
}
map.hello // correct, { type: 'hello' };
// If I access an unknown property, then the compiler should:
map.bar // `property bar doesn't exist on type { hello: { ... }, world: {...}, foo: {...} }`
如何为该功能编写类型?
答案 0 :(得分:2)
您可以首先使T
变得通用:
function toMap<T extends { type: string }>(...args: T[]): { [type: string]: T } {
return args.reduce((res, t) => ({
...res,
[t.type]: t
}), {});
}
然后才能真正缩小类型,您必须为变量参数键入通用类型,例如toMap<A>(arg1: A)
,toMap<A, B>(arg1: A, arg2: B)
。
尽管有两个缺点:
1)您必须为任意数量的参数创建这些重载,
但是在Typescript中很常见(请参见Object.assign
声明)。
2)打字稿默认将{ type: "test" }
的类型为{ type: string }
(在99%的情况下是必需的),但是因此我们不能直接将键类型推断为"test"
。为了解决这个问题,我们必须将字符串文字转换为缩小的字符串类型{ type: "test" as "test" }
。
// generic overload for one argument
function toMap<A>(arg: A): { [K1 in O<A>]: A };
// generic overload for two arguments:
function toMap<A, B>(arg: A, arg2: B): { [K in O<A>]: A } & { [K in O<B>]: B };
// generic overload for three arguments:
function toMap<A, B, C>(arg: A, arg2: B, arg3: C): { [K in O<A>]: A } & { [K in O<B>]: B } & { [K in O<C>]: C };
// ... repeat for more arguments
// implementation for all kind of args
function toMap<T extends { type: string }>(...args: T[]): { [type: string]: T } {
return args.reduce((res, t) => ({
...res,
[t.type]: t
}), {});
}
// Infers the type of "type", which has to be a string, from a given object
type O<V> = V extends { type: infer K } ? K extends string ? K : never : never;
// Narrow down a.type to be "test" instead of string
const a = { type: "test" as "test" }
const b = { type: "test2" as "test2", v: 1 };
const test = toMap(a);
const test2 = toMap(a, b);
console.log(
test2.test2.v, // works!
test2.whatever, // doesnt!
test2.test2.k // doesnt!
);