Javascript TypeScript:构造函数中的位置参数或命名参数

Javascript TypeScript:构造函数中的位置参数或命名参数,javascript,typescript,class,constructor,constructor-overloading,Javascript,Typescript,Class,Constructor,Constructor Overloading,我有一个类,目前正在采取7+位置参数 类用户{ 构造函数(参数1、参数2、参数3等){ // … } } 我想通过选项对象将其转换为命名参数 type UserOptions={ 参数1:字符串 // … } 类用户{ 构造函数({param1,param2,param3,…etc}={}:UserOptions){ // … } } 这很好,只是现在有很多测试需要修改以更改签名,所以我想同时支持命名参数和位置参数 我可以让代码同时支持这两种类型,但我不确定如何在不写出所有类型两次的情况下获

我有一个类,目前正在采取7+位置参数

类用户{
构造函数(参数1、参数2、参数3等){
// …
}
}
我想通过选项对象将其转换为命名参数

type UserOptions={
参数1:字符串
// …
}
类用户{
构造函数({param1,param2,param3,…etc}={}:UserOptions){
// …
}
}
这很好,只是现在有很多测试需要修改以更改签名,所以我想同时支持命名参数和位置参数

我可以让代码同时支持这两种类型,但我不确定如何在不写出所有类型两次的情况下获取其中的类型。理想情况下,my
UserOptions
中的类型列表将按照在
UserOptions
中定义的顺序成为位置参数

有没有办法做到这一点

type UserOptions={
参数1:字符串
// …
}
类型拓扑位置参数=[
//??类似于……对象值(T)
//以某种方式获取键作为位置参数名称???
]
键入PositionalUserOptions=ToPositionalParameters
类用户{
构造函数(…参数:PositionUserOptions);
构造函数(选项:UserOptions);
构造函数(…参数:[UserOptions]| PositionalUserOptions){
// …
}
}

也会考虑用另一种方式来解决问题,即位置参数被命名,也许这更容易?

这里有一些阻碍你的事情。

首先,对象类型中属性的顺序在TypeScript中目前是不可观察的,因为它们不影响可分配性。例如,类型系统中的类型
{a:string,b:number}
{b:number,a:string}
之间没有区别。您可以执行以下操作来梳理编译器中的信息,以查看它是否将键表示为
[“a”,“b”]
vs
[“b”,“a”]
,但所做的只是在编译时给您一些排序;当您自上而下阅读类型声明时,不能保证它是有序的。。。见鬼,它甚至不能保证每次编译时都是相同的顺序!请参阅,了解一个公开问题,以要求有一致的订购。请参阅一个看似无关的代码更改示例,它改变了预期的顺序。目前,我们无法以任何一致的方式自动将对象类型转换为有序元组

第二,函数类型中的参数名称故意不可用。它们是不可观察的,原因与对象属性排序类似:它们不影响可分配性。例如,在类型系统中,函数类型
(foo:string)=>void
(bar:string)=>void
之间没有区别。我甚至不认为有什么把戏可以揭露这样的细节。在类型系统中,函数参数名称仅用作文档或IntelliSense。您可以在命名函数参数和之间进行转换,但仅此而已。请参阅解释,我们不能在TypeScript中将调用签名参数名或元组标签用作字符串。目前,我们无法自动将带标签的元组转换为对象类型,其中对象的键对应于元组的标签


如果我们不尝试将一个对象转换为一个元组或将一个元组转换为一个对象,而是提供足够的信息来完成这两项工作,那么您可以回避这两个问题:

const userOptionKeys = ["param1", "param2", "thirdOne"] as const;
type PositionalUserOptions = [string, number, boolean];
这里的
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;
} */
现在我们可以编写
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
args_1
的标签。如果希望这些参数是
param1
param2
,只需咬紧牙关,写出两次参数名;一次作为字符串文本,另一次作为元组标签,因为无法将一个转换为另一个:

const userOptionKeys = ["param1", "param2", "thirdOne"] as const;
type PositionalUserOptions = [param1: string, param2: number, thirdOne: boolean];
值得吗?大概这取决于你

您可以这样做,但TypeScript团队不鼓励您使用这些工具,它们依赖于某些内部类型的实例化顺序(至少这是建议的)

尽管如此,弄明白这一点还是很有趣的,所以你来吧:

//通过https://github.com/Microsoft/TypeScript/issues/13298#issuecomment-707364842
类型UnionToTuple=(
(
(
T扩展任何
?(t:t)=>t
:从不
)扩展推断U
?(U)扩展任何
?(u:u)=>任何
:从不
)扩展(v:推断v)=>任何
v
:从不
:从不
)扩展(任意)=>推断W
?[…联合整数,W]
: []
);
类型Head=T扩展[推断A,…任意]?A:从来没有
类型Tail=T扩展[任何,…推断A]?答:从来没有;
类型PullFieldTypes=\u PullFieldType
类型(类型)<
T扩展对象,
CurrentKey,
RemainingKeys扩展任何[],
结果扩展任何[]
>=RemainingKeys['length']扩展0
? CurrentKey扩展了keyt?[…结果,T[CurrentKey]]:从不
:CurrentKey扩展了keyof T
/* && */? 剩余键扩展(keyof T)[]
? […结果,T[CurrentKey],…(字段类型]
:从不:从不;
//--IMPL--
类型Args={
参数1:字符串,
参数2:n
const userOptionKeys = ["param1", "param2", "thirdOne"] as const;
type PositionalUserOptions = [param1: string, param2: number, thirdOne: boolean];