停留在GroupBy函数的typescript泛型签名上



几个小时后,我一直在思考这个TS签名的错误。

我正在按函数编写一个组:

const group = <T>(items: T[], fn: (item: T) => T[keyof T]) => {
return items.reduce((prev, next) => {
const prop = fn(next) as unknown as string;
return {
...prev,
[prop]: prev[prop] ? [...prev[prop], next] : [next],
};
}, {} as any);
};
group(data, (item) => item.type);

返回类型为:

const group: <Item>(items: Item[], fn: (item: Item) => string) => any

我想要的是:

const group: <Item>(items: Item[], fn: (item: Item) => string) => { admin: Item[], user: Item[] }

以下是数据结构:

interface User {
type: string;
name: string;
}
const data: User[] = [
{ type: 'admin', name: 'One' },
{ type: 'user', name: 'Two' },
{ type: 'admin', name: 'Three' },
];

我尝试过这样的方法(将对象传递到reduce中(,但它给了我一个错误,我确信"修复"是什么:

{} as { [key: T[keyof T]: T[]] }

这是一个运行代码为的TS游乐场

干杯!

我注意到"管理员";以及";用户";只能从数据阵列的值(data[number]['type']的可能值(中获得
TypeScript默认情况下不会从值中假设太多,而值是字符串。("类型"one_answers"名称"的情况不同,因为它们是从密钥中获得的(

但是,如果对数组使用as const,TypeScript也会推断出对值的更多限制
假设User['type']的有效值是有限的,可以限制它,如下所示:

let PossibleUserTypes = ['admin', 'user'] as const;  
interface User {
type: typeof PossibleUserTypes[number];
name: string;
}
const data: User[] = [
{ type: 'admin', name: 'One' },
{ type: 'user', name: 'Two' },
{ type: 'admin', name: 'Three' },
];
const group = <T extends User, U extends string>(items: T[], fn: (item: T) => U) => {
return items.reduce((prev, next) => {
const prop = fn(next)
return {
...prev,
[prop]: prev[prop] ? [...prev[prop], next] : [next],
};
}, {} as {[x in U] : T[] } );
};
let temp = group(data, (item) => item.type);
console.log(temp);
/*
inferred typing: 
let temp: {
admin: User[];
user: User[];
}
*/

如果删除"as const"部分,输出将仅变为{ [x: string]: User[];},因为它不假设x的值(来自"type"的值(可以限制为什么。

n.b.您也可以直接使用type: 'admin' | 'user'来代替type: typeof PossibleUserTypes[number];;问题是,它不再只是";任何字符串";就像你的原始代码一样。

另一种选择是使用{} as Record<U,T[]>(这里temp将被推断为Record<'user'|'admin', User[]>,它仍然允许您获得从temp.temp.admintemp.user的代码完成(,如果您删除as const,您将失去限制(因此不再只是.admin.user(。

可以尝试将as const用于data数组,而不是PossibleUserTypes,但结果类型将非常复杂(您可以自己检查(:

const data  = [
{ type: 'admin', name: 'One' },
{ type: 'user', name: 'Two' },
{ type: 'admin', name: 'Three' },
] as const 

const group = <T, U extends string>(items: Readonly<T[]>, fn: (item: T) => U) => {
return items.reduce((prev, next) => {
const prop = fn(next)
return {
...prev,
[prop]: prev[prop] ? [...prev[prop], next] : [next],
};
}, {} as {[x in U] : T[] } );
};
let temp = group(data, (item) => item.type);
console.log(temp)

在函数签名中,您不指定函数的返回类型。你只需要像这样返回类型T:

const group = <T>(items: T[], fn: (item: T) => T[keyof T]):T => { return items.reduce((prev, next) => {
const prop = fn(next) as unknown as string;
return {
...prev,
[prop]: prev[prop] ? [...prev[prop], next] : [next],
};}, {} as any);};
console.log(group<Item>(data, (item) => item.type));

看这个打字游戏场TS游乐场

参考您的评论,我对代码做了一些更改。在这里检查返回的类型必须是静态类型。

最新更新