我已经坚持了几个小时。我基本上是在尝试创建一个将打字稿类型转换为查询字符串的函数。我从我在网上找到的一个例子开始,但我似乎无法让它工作。我看到了下面的错误。
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)
答案 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)[]
的保留仍然有效。它仅在类型变量的域中有意义(即 T
和 keyof 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
数组:虽然对象现在确实有定义的属性顺序,但它很复杂(不仅取决于属性何时添加,还取决于键的实际值)和使用它很少有用。