没有重载匹配这个调用.在Typescript中使用Object reduce回调时



这个问题我已经纠结了好几个小时了。我基本上试图创建一个函数,将typescript类型转换为查询字符串。我从一个我在网上找到的例子开始,但我似乎不能让它工作。我看到下面的错误。

import querystring from 'querystring';

type AuthQuery = {
code: string;
timestamp: string;
state: string;
shop: string;
host?: string;
hmac?: string;
}
export function stringifyQuery(query: AuthQuery): string {
const orderedObj = Object.keys(query)
.sort((val1, val2) => val1.localeCompare(val2))
.reduce((obj: Record<string, string | undefined>, key: keyof AuthQuery) => {
obj[key] = query[key];
return obj;
}, {});
return querystring.stringify(orderedObj);
}

得到如下结果:

No overload matches this call.
Overload 1 of 3, '(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string', gave the following error.
Argument of type '(obj: Record<string, string | undefined>, key: keyof AuthQuery) => Record<string, string | undefined>' is not assignable to parameter of type '(previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string'.
Types of parameters 'obj' and 'previousValue' are incompatible.
Type 'string' is not assignable to type 'Record<string, string | undefined>'.
Overload 2 of 3, '(callbackfn: (previousValue: Record<string, string | undefined>, currentValue: string, currentIndex: number, array: string[]) => Record<string, string | undefined>, initialValue: Record<...>): Record<...>', gave the following error.
Argument of type '(obj: Record<string, string | undefined>, key: keyof AuthQuery) => Record<string, string | undefined>' is not assignable to parameter of type '(previousValue: Record<string, string | undefined>, currentValue: string, currentIndex: number, array: string[]) => Record<string, string | undefined>'.
Types of parameters 'key' and 'currentValue' are incompatible.
Type 'string' is not assignable to type 'keyof AuthQuery'.(2769)

问题是reduce不知道它被调用的数组是keyof AuthQuery值;它认为它们是字符串,因为这就是Object.keys提供的。您不能通过在reduce调用的key参数上添加类型来修复这个问题。不幸的是,您必须使用类型断言,请参阅下面我创建sortedKeys的地方(它不必是自己的单独变量,但事情变得笨拙):

export function stringifyQuery(query: AuthQuery): string {
const sortedKeys = Object.keys(query)
.sort((val1, val2) => val1.localeCompare(val2)) as (keyof AuthQuery)[];
const orderedObj = sortedKeys
.reduce((obj: Record<string, string | undefined>, key) => {
obj[key] = query[key];
return obj;
}, {});
return querystring.stringify(orderedObj);
}

我很想把Record<string, string | undefined>移到末尾,作为{}种子值的类型断言,如下所示:

export function stringifyQuery(query: AuthQuery): string {
const sortedKeys = Object.keys(query)
.sort((val1, val2) => val1.localeCompare(val2)) as (keyof AuthQuery)[];
const orderedObj = sortedKeys
.reduce((obj, key) => {
obj[key] = query[key];
return obj;
}, {} as Record<string, string | undefined>);
return querystring.stringify(orderedObj);
}

…但是它可以在你有它的地方工作,所以我把它留在那里。

我不会使用reduce,尽管reduce只会使这段代码复杂化。下面是一个使用简单循环的版本:

export function stringifyQuery(query: AuthQuery): string {
const sortedKeys = Object.keys(query)
.sort((val1, val2) => val1.localeCompare(val2)) as (keyof AuthQuery)[];
const orderedObj: Record<string, string | undefined> = {};
for (const key of sortedKeys) {
orderedObj[key] = query[key];
}
return querystring.stringify(orderedObj);
}

你可能想知道为什么Object.keys不是这样定义的:

function keys<T>(object: T): (keyof T)[];

这样做被考虑和拒绝,显然是因为安德斯·海尔斯伯格的观察(巨大)受人尊敬的语言设计师,Turbo Pascal的创建者,Delphi的首席架构师,c#的首席架构师,TypeScript的核心开发者):

我对Object.keys返回(keyof T)[]的保留意见仍然有效。它只在类型变量(即Tkeyof T)的域中有意义。一旦进入实例化类型的世界,它就退化了,因为对象在运行时可以(而且经常)拥有比编译时静态已知的更多的属性。例如,想象一个类型Base,它只有几个属性和由它派生的一系列类型。用Base调用Object.keys将返回(keyof Base)[],这几乎肯定是错误的,因为实际实例将是具有更多键的派生对象。它完全退化为{}类型,它可以是任何对象,但将返回never[]作为其键。

如果你想忽视圣人的智慧-你的危险!你可以为它提供一个包装器:

function staticKeys<T>(object: T) {
return Object.keys(object) as (keyof T)[];
}

然后为上面的操作获得排序的键是staticKeys(query).sort(/*...*/),在调用站点没有类型断言。


关于Object.keys数组的排序:虽然对象现在确实有一个定义的属性顺序,但它很复杂(不仅取决于属性添加的时间,还取决于键的实际值),使用它很少有用。

最新更新