英文:
Is there a way to change this typescript function so that the resultant object is typed with only the keys in the map?
问题
这个函数将返回一个类型为{[key: string]: T[]}
的对象。
但是我想要提高这个函数的类型安全性,以便在IDE中能够智能提示生成的对象中的有效键。
我一直在尝试将键作为常量值获取并用它来为对象定义类型,但我无法弄清楚使其工作的正确语法,也不知道这是否是正确的方法。
export function groupBy<T>(array: T[], getKey: (item: T) => string) {
const map = new Map<string, T[]>();
array.forEach((item) => {
const key = getKey(item);
const group = map.get(key);
group ? group.push(item) : map.set(key, [item]);
});
return Object.fromEntries(map.entries()) as {[key: string]: T[]};
}
英文:
The function will return an object of type {[key: string] : T[]}
But I am looking to improve the type safety of this function so that in the IDE I get IntelliSense on what keys are valid in the resulting object.
I've been trying to get the keys as a const value and use that to type the object, but I can't figure out the correct syntax to get it working, nor do I know if that is even a correct approach.
export function groupBy<T>(array: T[], getKey: (item: T) => string) {
const map = new Map<string, T[]>();
array.forEach((item) => {
const key = getKey(item);
const group = map.get(key);
group ? group.push(item) : map.set(key, [item]);
});
return Object.fromEntries(map.entries());
}
答案1
得分: 1
以下是您提供的内容的中文翻译:
要使这个工作起作用,您希望groupBy()
不仅在T
(数组的元素类型)中是通用的,还在K
(getKey()
的返回类型)中是通用的。像这样:
function groupBy<T, K extends string>(array: T[], getKey: (item: T) => K) {
const map = new Map<string, T[]>();
array.forEach((item) => {
const key = getKey(item);
const group = map.get(key);
group ? group.push(item) : map.set(key, [item]);
});
return Object.fromEntries(map.entries()) as { [P in K]: T[] }
}
这里的K
被约束为string
,所以如果getKey()
返回非string
,编译器将会报错。但现在,当您调用groupBy()
时,编译器将尝试推断K
为getKey()
可以返回的特定值对应的字符串字面类型的联合类型。
然后,groupBy()
的返回类型是{ [P in K]: T[] }
(使用Record
实用工具类型而不是{ [k: string]: T[] }
等效)。这意味着它具有类型为K
的键和类型为T[]
的值。请注意,我需要断言返回的值由Object.entries()
生成,因为TypeScript编译器无法验证这一点(请参阅https://stackoverflow.com/q/62053739/2887218或其他问题以获取更多信息)。
让我们通过一个示例来测试它:
interface Foo {
name: string;
age: number;
}
const foos: Foo[] = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 35 },
{ name: "Carol", age: 40 },
{ name: "Dave", age: 50 }
];
const groups = groupBy(
foos,
(foo) => foo.age >= 40 ? "older" : foo.name.length <= 4 ? "shortname" : "other"
);
/* const groups: {
older: Foo[];
shortname: Foo[];
other: Foo[];
} */
console.log("Older: " + groups.older.map(foo => foo.name).join(", "));
// Older: Carol, Dave
console.log("Shortname: " + groups.shortname.map(foo => foo.name).join(", "))
// Shortname: Bob
groups.randomKey; // error
看起来不错。编译器推断K
为联合类型"older" | "shortname" | "other"
,因此返回的groups
值的类型为{older: Foo[]; shortname: Foo[]; other: Foo[]}
。因此,它允许您索引groups.older
和groups.shortname
,但不允许您索引groups.randomKey
,这应该是您期望的行为。
英文:
For this to work you want groupBy()
to be generic not only in T
, the element type of array
, but also in K
, the return type of getKey()
. Like this:
function groupBy<T, K extends string>(array: T[], getKey: (item: T) => K) {
const map = new Map<string, T[]>();
array.forEach((item) => {
const key = getKey(item);
const group = map.get(key);
group ? group.push(item) : map.set(key, [item]);
});
return Object.fromEntries(map.entries()) as { [P in K]: T[] }
}
Here K
is constrained to string
, so the compiler will complain if getKey()
returns a non-string
. But now when you call groupBy()
, the compiler will try to infer K
to be a union of string literal types corresponding to the particular values that getKey()
can return.
Then the return type of groupBy()
is { [P in K]: T[] }
(equivalent to Record<K, T[]>
using the Record
utility type instead of { [k: string]: T[] }
. That means it has keys of type K
and values of type T[]
. Note that I needed to assert that the value returned by Object.entries()
is of that type, since the TypeScript compiler can't verify that (see https://stackoverflow.com/q/62053739/2887218 or other questions for more information).
Let's test it out with an example:
interface Foo {
name: string;
age: number;
}
const foos: Foo[] = [
{ name: "Alice", age: 30 },
{ name: "Bob", age: 35 },
{ name: "Carol", age: 40 },
{ name: "Dave", age: 50 }
];
const groups = groupBy(
foos,
(foo) => foo.age >= 40 ? "older" : foo.name.length <= 4 ? "shortname" : "other"
);
/* const groups: {
older: Foo[];
shortname: Foo[];
other: Foo[];
} */
console.log("Older: " + groups.older.map(foo => foo.name).join(", "));
// Older: Carol, Dave
console.log("Shortname: " + groups.shortname.map(foo => foo.name).join(", "))
// Shortname: Bob
groups.randomKey; // error
Looks good. The compiler infers that K
is the union "older" | "shortname" | "other"
and therefore that the returned groups
value is of type {older: Foo[]; shortname: Foo[]; other: Foo[]}
. And so it lets you index into groups.older
and groups.shortname
but it wouldn't let you index into groups.randomKey
, which is I think what your desired behavior is.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论