几个小时后,我一直在思考这个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.admin
和temp.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游乐场
参考您的评论,我对代码做了一些更改。在这里检查返回的类型必须是静态类型。