Typescript中的类构造函数重载

7qhs6swi  于 11个月前  发布在  TypeScript
关注(0)|答案(2)|浏览(136)

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

  1. PlaybackControlButton: HTMLButtonElement
  2. PlaybackControlMenu: HTMLDivElement
    现在,要初始化类对象,我需要三个参数:
  3. videoPlayer: HTMLVideoElement
  4. playbackRates: PlaybackRates
  5. 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.未传递参数
    1.传递的参数的任何组合任何未给出的值都应回退到其默认值,
    最后,videoPlayer: HTMLVideoElement是唯一一个我想作为类属性存储的参数,其余两个只是我想在构造函数中进行一些函数调用的参数(因为我以后不会用到它们)。
    目前,我写的构造函数是:
constructor (videoPlayer?: HTMLVideoElement, playbackRates: PlaybackRates = PlaybackControl.DEFAULT_PLAYBACK_RATES, options: PlaybackControlOptions = { enableShortcuts: true, shortcuts: PlaybackControl.DEFAULT_SHORTCUTS })


虽然这确实允许我在没有任何参数的情况下初始化,但当我试图:
new PlaybackControl({ enableShortcuts: false })
VSCode显示错误:

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


虽然我确实理解潜在的问题(我猜),但我无法解决这个问题。任何帮助都很感激。
编辑:
1.因为,口头描述可能很难深入,在这里你可以找到要运行的整个代码。
1.通过any combination of arguments澄清:我应该能够给予任何参数,我想手动设置(以秩序与一些失踪)和其余的回退到默认值

lmvvr0a8

lmvvr0a81#

这里的主要问题是,你的构造函数的 * 实现 * 将是疯狂的,因为它会搜索正确类型的属性的输入。它本质上要求没有两个参数是相同的类型,因为否则会有歧义.对于编写的代码,实现可能类似于

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,以便可能缺少一些,所以,例如,如果videoPlayeroptions都存在,你就不会在videoPlayer之前传入options
你可以手动重载构造函数来接受每一个可能的输入组合,但是我喜欢玩类型,所以让我们写一个Subsequence<T>实用程序类型,它接受一个输入元组类型T,并产生T的每一个可能的subsequence的并集。
那么构造函数的输入可以是:

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链接到代码

xkftehaa

xkftehaa2#

如果我尝试以下操作时失败:

new PlaybackControl({ enableShortcuts: false })

字符串
我不会尝试重载构造函数来接受这个。它需要检查第一个参数是dom元素还是选项对象,然后将参数放入相应的变量中。这会导致相当丑陋的代码和复杂的类型签名。
相反,要跳过传递前两个参数,请为它们传递undefined,然后将选项对象作为第三个参数传递:

new PlaybackControl(undefined, undefined, { enableShortcuts: false })


如果这是常见用法,请考虑将构造函数更改为仅接受一个对象参数,以便调用

new PlaybackControl({ options: { enableShortcuts: false } })

相关问题