我正在处理一个类PlaybackControl,我用它来创建两个HTMLElement:

  1. PlaybackControlButton: HTMLButtonElement
  2. PlaybackControlMenu: HTMLDivElement

现在,要初始化类对象,我需要三个参数:

  1. videoPlayer: HTMLVideoElement
  2. playbackRates: PlaybackRates
  3. options: PlaybackControlOptions

其中:

type Shortcuts = Record<string, { key: string, value: string }>
type PlaybackRates = string[]

interface ShortcutsEnabled {
  enableShortcuts: true
  shortcuts: Shortcuts
}

interface ShortcutsDisabled {
  enableShortcuts: false
}

interface DefaultOptions extends ShortcutsEnabled {}

type PlaybackControlOptions = DefaultOptions | ShortcutsDisabled

此外,我对所有这些参数都有缺省值,

  1. videoPlayer默认为document.querySelector('video') as HTMLVideoElement
  2. playbackRates默认为静态属性PlaybackControl.DEFAULT_PLAYBACK_RATES
  3. options默认为{ enableShortcuts: true, shortcuts: PlaybackControl.DEFAULT_SHORTCUTS }

现在,我想创建一个重载的构造函数,它应该在所有情况下都能工作:

  1. 未传递任何参数
  2. 传递的参数的任何组合 任何没有给出的值都应该退回到它的缺省值,

最后,videoPlayer: HTMLVideoElement个是我想要存储为类属性的唯一参数,其余两个只是参数,我只想在构造函数中调用一些函数(因为我以后不会用到它们).

目前,我编写的构造函数是:

constructor (videoPlayer?: HTMLVideoElement, playbackRates: PlaybackRates = PlaybackControl.DEFAULT_PLAYBACK_RATES, options: PlaybackControlOptions = { enableShortcuts: true, shortcuts: PlaybackControl.DEFAULT_SHORTCUTS })

虽然这确实允许我在没有任何参数的情况下进行初始化,但当我try 执行以下操作时会失败:

new PlaybackControl({ enableShortcuts: false })

VSCode显示以下错误:

Object literal may only specify known properties, and 'enableShortcuts' does not exist in type 'HTMLVideoElement'.

虽然我确实理解潜在的问题(我想),但我无法解决这个问题. 如有任何帮助,我们不胜感激.

编辑:

  1. 由于口头描述可能很难深入,here您可以找到要运行的整个代码.

  2. 通过any combination of arguments%的澄清: 我应该能够给出我想要手动设置的任何参数(按顺序,有一些缺失),其余的后备为缺省

推荐答案

这里的主要问题是,您的implementation构造函数将会疯狂,因为它在输入中搜索正确类型的属性.它本质上要求没有两个参数是相同类型的,否则就会有歧义.如果你有a: string, b: string个呢?来电者写的是new X("c")吗?哪个参数应设置为"c"?对于所编写的代码,实现可能如下所示

class PlaybackControl {
  static DEFAULT_PLAYBACK_RATES = [];
  static DEFAULT_SHORTCUTS = {};
  constructor(
    ...args: ???) {
    const a: (HTMLVideoElement | PlaybackRates | PlaybackControlOptions)[] = args;
    const videoPlayer = a.find((x): x is HTMLVideoElement =>
      x instanceof HTMLVideoElement);
    const playbrackRates = a.find((x): x is PlaybackRates =>
      Array.isArray(x) && x.every(e => typeof e === "string")
    ) ?? PlaybackControl.DEFAULT_PLAYBACK_RATES;
    const options: PlaybackControlOptions = a.find((x): x is PlaybackControlOptions =>
      x && typeof x === "object" && "enableShortcuts" in x
    ) ?? { enableShortcuts: true, shortcuts: PlaybackControl.DEFAULT_SHORTCUTS };
  }
}

这应该是你喜欢的工作和行为方式,但它太脆弱了.


无论如何,假设您确实想要这样一个实现,那么仍然存在如何实现它的问题.您说输入必须是videoPlayer: HTMLVideoElement, playbackRates: PlaybackRates, options: PlaybackControlOptions,其中一些可能会丢失,因此,例如,如果optionsvideoPlayer都存在,则不会传入videoPlayer之前的options.

您只需手动将构造函数设置为overload,即可接受所有可能的输入组合.但我喜欢处理类型,所以让我们编写一个Subsequence<T>实用程序类型,它接受输入tuple type T,并生成T的每个可能的subsequenceunion.

则构造函数输入可以是:

type Subsequence<T extends any[]> =
  T extends [infer F, ...infer R] ? [F, ...Subsequence<R>] | Subsequence<R> : []

这给

type X = Subsequence<[videoPlayer: HTMLVideoElement, playbackRates: PlaybackRates, options: PlaybackControlOptions]>;
/* type X = [] | [PlaybackControlOptions] | [PlaybackRates] | [PlaybackRates, PlaybackControlOptions] | 
  [HTMLVideoElement] | [HTMLVideoElement, PlaybackControlOptions] | [HTMLVideoElement, PlaybackRates] | 
  [HTMLVideoElement, PlaybackRates, PlaybackControlOptions] */

因此,完整的构造函数如下所示

class PlaybackControl {
  static DEFAULT_PLAYBACK_RATES = [];
  static DEFAULT_SHORTCUTS = {};
  constructor(
    ...args: Subsequence<[videoPlayer: HTMLVideoElement, playbackRates: PlaybackRates, options: PlaybackControlOptions]>
  ) {
    const a: (HTMLVideoElement | PlaybackRates | PlaybackControlOptions)[] = args;
    const videoPlayer = a.find((x): x is HTMLVideoElement =>
      x instanceof HTMLVideoElement);
    const playbrackRates = a.find((x): x is PlaybackRates =>
      Array.isArray(x) && x.every(e => typeof e === "string")
    ) ?? PlaybackControl.DEFAULT_PLAYBACK_RATES;
    const options: PlaybackControlOptions = a.find((x): x is PlaybackControlOptions =>
      x && typeof x === "object" && "enableShortcuts" in x
    ) ?? { enableShortcuts: true, shortcuts: PlaybackControl.DEFAULT_SHORTCUTS };
  }
}

它的行为达到了预期的效果.

new PlaybackControl({ enableShortcuts: false })

但是,这值得吗?几乎可以肯定的是,不会.疯狂的实现加上疯狂的类型等于太疯狂了.完成这类操作的传统方法是接受一个类型为{videoPlayer?: HTMLVideoElement, playbackRates?: PlaybackRates, options?: PlaybackControlOptions}的对象输入,以利用JavaScript中的对象对属性顺序的自然无关性.歧义消失了(例如,{a?: string, b?: string}需要{a: "c"}{b: "c"},并且打字非常简单.因此,你应该转而这么做.

Playground link to code

Javascript相关问答推荐

用户单击仅在JavaScript中传递一次以及其他行为

容器如何更改默认插槽中子项的显示?

提交表格后保留Web表格中的收件箱值?

GrapeJS -如何保存和加载自定义页面

JQuery. show()工作,但. hide()不工作

如何在不创建新键的情况下动态更改 map 中的项目?

Spring boot JSON解析错误:意外字符错误

如何调用名称在字符串中的实例方法?

查找最长的子序列-无法重置数组

为什么Mutations 观察器用微任务队列而不是macrotask队列处理?

编剧如何获得一个div内的所有链接,然后判断每个链接是否都会得到200?

无法使用单击按钮时的useState将数据从一个页面传递到另一个页面

MongoDB受困于太多的数据

Google脚本数组映射函数横向输出

处理TypeScrip Vue组件未初始化的react 对象

如果对象中的字段等于某个值,则从数组列表中删除对象

Django模板中未加载JavaScript函数

输入数据覆盖JSON文件

当一条路由在Reaction路由中命中时,如何有条件地渲染两个组件或更改两个插座?

如何从嵌套的json中获取最大值