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

标签 javascript typescript class constructor constructor-overloading

我有一个类目前采用 7 个以上的位置参数。

class User {
  constructor (param1, param2, param3, …etc) {
    // …
  }
}

我想通过选项对象将其转换为命名参数。

type UserOptions = {
  param1: string
  // …
}

class User {
  constructor ({ param1, param2, param3, …etc } = {}: UserOptions) {
    // …
  }
}

这很好,除了现在需要重新进行大量测试以更改签名,所以我想同时支持命名参数和位置参数。

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

有没有办法做这样的事情?

type UserOptions = {
  param1: string
  // …
}

type ToPositionalParameters<T> = [
  // ??? something like ...Object.values(T)
  // and somehow getting the keys as the positional argument names???
]

type PositionalUserOptions = ToPositionalParameters<UserOptions>

class User {
  constructor (...args: PositionalUserOptions);
  constructor (options: UserOptions);
  constructor (...args: [UserOptions] | PositionalUserOptions) { 
    // …
  }
}

也会考虑一个相反的解决方案,即命名的位置参数,也许这更容易?

最佳答案

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

首先是对象类型中的属性顺序目前在 TypeScript 中不可观察,因为它们不影响可分配性。例如,类型系统中的{a: string, b: number}{b: number, a: string}没有区别。有dirty tricks你可以从编译器中提取信息,看看它是否将键表示为 ["a", "b"] vs ["b", "a"],但所做的只是在编译时为您提供一些排序;当您从上到下阅读类型声明时,它不能保证是有序的...哎呀,甚至不能保证每次编译时相同的顺序!参见 microsoft/TypeScript#17944对于一个开放的问题,要求有一个一致的顺序。并查看 microsoft/TypeScript#42178例如,看似无关的代码更改会改变预期的顺序。目前,我们无法以任何一致的方式自动将对象类型转换为有序元组。

第二个是函数类型中的参数名称是有意不可用string literal types .它们是不可观察的,原因与对象属性排序类似:它们不影响可分配性。例如,类型系统中的函数类型(foo: string) => void(bar: string) => void 没有区别。我什至不认为有任何技巧可以暴露这些细节。在类型系统中,函数参数名称仅用作文档或 IntelliSense。您可以在命名函数参数和 labeled tuple elements 之间进行转换,仅此而已。参见 this comment in microsoft/TypeScript#28259解释我们不能在 TypeScript 中使用调用签名参数名称或元组标签作为字符串。目前,我们无法自动将带标签的元组转换为对象类型,其中对象的键对应于元组的标签。


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

const userOptionKeys = ["param1", "param2", "thirdOne"] as const;
type PositionalUserOptions = [string, number, boolean];

此处 userOptionKeysUserOptions 对象中所需键的明确排序列表...与我们手动创建的 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
}  */

有效!从类型系统和可分配性的 Angular 来看,这是您能做的最好的事情。从文档/IntelliSense 的 Angular 来看,这不是很好。当您调用 new User() 时,多参数版本的文档将为您提供 args_0args_1 之类的标签。如果您希望它们是param1param2,您只需要硬着头皮将参数名称写出两次;一次作为字符串文字,一次作为元组标签,因为无法将一个转换为另一个:

const userOptionKeys = ["param1", "param2", "thirdOne"] as const;
type PositionalUserOptions = [param1: string, param2: number, thirdOne: boolean];

值得吗?也许……这取决于您。

Playground link to code

关于javascript - TypeScript:构造函数中的位置或命名参数,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/66144332/

相关文章:

php - 是否有一个 call_user_func() 相当于创建一个新的类实例?

java - 为 Java 中的每个单元测试重新初始化枚举值

javascript - 我想安装 Angular 6 而不是 Angular 8。我该怎么办?

javascript - Django 静态 js 文件不起作用

typescript - 去掉 Vscode 的 inline window on cmd click 直接导航到 code

javascript - 带有 polyfilled Function.name 的 IE11 Javascript 行为

c++ - 类指针本身有类成员吗?

javascript - 如何在 Jquery ui datepicker 中显示日期格式?

javascript - 在 Javascript 代码中调用 php 函数

typescript - 部署到heroku期间出现Vite错误