这里有一些东西阻碍了你。
第一个是对象类型中属性的顺序当前是不可观察的在 TypeScript 中,因为它们不影响可分配性。例如,类型之间没有区别{a: string, b: number}
and {b: number, a: string}
在类型系统中。有肮脏的伎俩 https://stackoverflow.com/questions/55127004/how-to-transform-union-type-to-tuple-type您可以执行从编译器中提取信息以查看它是否将键表示为["a", "b"]
vs ["b", "a"]
,但所做的只是给你some编译时排序;当您从上到下阅读类型声明时,并不能保证它是有序的......哎呀,它甚至不能保证是the same每次编译时都要订购!看微软/TypeScript#17944 https://github.com/microsoft/TypeScript/issues/17944对于一个悬而未决的问题,要求有一个一致的顺序。并看到微软/TypeScript#42178 https://github.com/microsoft/TypeScript/issues/42178一个看似无关的代码更改的示例,它改变了预期的顺序。目前,我们无法以任何一致的方式自动将对象类型转换为有序元组。
第二个是函数类型中的参数名称是故意的无法使用 as 字符串文字类型 https://www.typescriptlang.org/docs/handbook/literal-types.html#string-literal-types。它们是不可观察的,原因与对象属性排序类似:它们不影响可分配性。例如,函数类型之间没有区别(foo: string) => void
and (bar: string) => void
在类型系统中。我什至不认为有任何技巧可以暴露这些细节。在类型系统中,函数参数名称仅用作文档或 IntelliSense。您可以在命名函数参数和标记元组元素 https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-0.html#labeled-tuple-elements,但仅此而已。看microsoft/TypeScript#28259 中的此评论 https://github.com/microsoft/TypeScript/issues/28259#issuecomment-671741399解释我们不能在 TypeScript 中使用调用签名参数名称或元组标签作为字符串。目前,我们无法自动将带标签的元组转换为对象类型,其中对象的键对应于元组的标签。
如果我们不尝试将对象转换为元组或将元组转换为对象,而是提供足够的信息来完成这两件事,那么您可以回避这两个问题:
const userOptionKeys = ["param1", "param2", "thirdOne"] as const;
type PositionalUserOptions = [string, number, boolean];
Here userOptionKeys
是所需键的显式有序列表UserOptions
对象...与我们手动创建的顺序相同PositionalUserOptions
元组。现在我们有了关键名称,我们可以构建UserOptions
:
type UserOptions = { [I in Exclude<keyof PositionalUserOptions, keyof any[]> as
typeof userOptionKeys[I]]: PositionalUserOptions[I] }
/* type UserOptions = {
param1: string;
param2: number;
thirdOne: boolean;
} */
当我们这样做时,我们可以编写一个函数来转换类型的元组PositionalUserOptions
进入一个类型的对象UserOptions
(与any
类型断言使编译器不必尝试验证它,而这是不容易做到的):
function positionalToObj(opts: PositionalUserOptions): UserOptions {
return opts.reduce((acc, v, i) => (acc[userOptionKeys[i]] = v, acc), {} as any)
}
现在我们可以这样写User
类,使用positionalToObj
在构造函数的实现中规范化事物:
class User {
constructor(...args: PositionalUserOptions);
constructor(options: UserOptions);
constructor(...args: [UserOptions] | PositionalUserOptions) {
const opts = args.length === 1 ? args[0] : positionalToObj(args);
console.log(opts);
}
}
new User("a", 1, true);
/* {
"param1": "a",
"param2": 1,
"thirdOne": true
} */
有用!从类型系统和可分配性的角度来看,这是您能做的最好的事情。从文档/智能感知的角度来看,这并不是很好。你打电话时new User()
,多参数版本的文档将为您提供类似的标签args_0
and args_1
。如果你想要那些param1
and param2
,你只需要硬着头皮将参数名称写出两次即可;一次作为字符串文字,再次作为元组标签,因为无法将一个转换为另一个:
const userOptionKeys = ["param1", "param2", "thirdOne"] as const;
type PositionalUserOptions = [param1: string, param2: number, thirdOne: boolean];
这值得么?也许……这取决于你。