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

时间:2021-07-28 12:01:52

标签: 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)


1 个答案:

答案 0 :(得分:1)

问题是 reduce 不知道它被调用的数组是 keyof AuthQuery 值;它认为它们是字符串,因为这就是 Object.keys 提供的。您无法通过在 key 调用中的 reduce 参数上放置类型来解决此问题。不幸的是,您必须使用类型断言,请参阅我在下面创建 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)[];

这样做被考虑并拒绝了,显然是因为 this observation by Anders Hejlsberg非常受人尊敬的语言设计师,Turbo Pascal 的创造者,Delphi 的首席架构师,C# 的首席架构师,以及打字稿):

<块引用>

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

如果你想忽视那句圣人的智慧——后果自负! — 您可以为自己提供一个包装器:

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

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


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