提问人:D V 提问时间:11/18/2023 最后编辑:D V 更新时间:11/19/2023 访问量:63
Typescript 中的类构造函数重载
Class constructor overloading in Typescript
问:
我正在开发一个类,我用它来创建两个HTMLElements:PlaybackControl
PlaybackControlButton: HTMLButtonElement
PlaybackControlMenu: HTMLDivElement
现在,为了初始化类对象,我需要三个参数:
videoPlayer: HTMLVideoElement
playbackRates: PlaybackRates
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
另外,我都有所有这些的默认值,
videoPlayer
默认为document.querySelector('video') as HTMLVideoElement
playbackRates
默认为静态属性PlaybackControl.DEFAULT_PLAYBACK_RATES
options
默认为{ enableShortcuts: true, shortcuts: PlaybackControl.DEFAULT_SHORTCUTS }
现在,我想创建一个重载的构造函数,它应该在所有情况下都有效:
- 未传递任何参数
- 传递的参数的任意组合 任何未给出的值都应回退到其默认值,
最后,是我唯一想存储为类属性的参数,其余两个只是我只想在构造函数中调用一些函数的参数(因为我以后没有用到它们)。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'.
虽然我确实了解潜在的问题(我猜),但我无法解决这个问题。 任何帮助都是值得赞赏的。
编辑:
由于口头描述可能很难深入研究,因此您可以在此处找到要运行的整个代码。
澄清一下: 我应该能够给出我想手动设置的任何参数(按顺序进行,缺少一些参数),其余的回退到默认值
any combination of arguments
答:
当我尝试以下操作时,这会失败:
new PlaybackControl({ enableShortcuts: false })
我不会尝试重载构造函数来接受这一点。它需要检查第一个参数是 dom 元素还是 options 对象,然后将参数洗牌到相应的变量中。这导致了相当丑陋的代码和复杂的类型签名。
相反,要跳过传递前两个参数,请传递它们,然后将选项对象作为第三个参数传递:undefined
new PlaybackControl(undefined, undefined, { enableShortcuts: false })
如果这是常见用法,请考虑将构造函数更改为仅接受一个对象参数,以便调用
new PlaybackControl({ options: { enableShortcuts: false } })
评论
undefined
undefined
constructor ()
constructor (videoPlayer: HTMLVideoElement)
constructor (playbackRates: PlaybackRates)
constructor (options: PlaybackControlOptions)
constructor (videoPlayer: HTMLVideoElement, playbackRates: PlaybackRates options: PlaybackControlOptions) {}
这里的主要问题是构造函数的实现会很疯狂,因为它在输入中搜索正确类型的属性。它本质上要求没有两个参数属于同一类型,否则就会有歧义......如果你有呢?来电者写?应将哪个参数设置为?对于编写的代码,实现可能类似于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
options
videoPlayer
您可以手动重载构造函数以接受每个可能的输入组合。但是我喜欢玩类型,所以让我们编写一个实用程序类型,它采用输入元组类型并生成 的每个可能的子序列的并集。Subsequence<T>
T
T
然后构造函数输入可以是:
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 })
但是,这值得吗?几乎可以肯定不是。疯狂的实现加上疯狂的打字等于太多疯狂。做这样的事情的传统方法是采用单个类型的对象输入,以利用 JavaScript 中对象为您提供的对属性顺序的自然无动于衷。歧义消失了(例如,需要 either 或 ,并且键入非常简单。所以你应该这样做。{videoPlayer?: HTMLVideoElement, playbackRates?: PlaybackRates, options?: PlaybackControlOptions}
{a?: string, b?: string}
{a: "c"}
{b: "c"}
上一个:ReportLab 的页眉和页脚
评论
arg: {videoPlayer?: HTMLVideoElement, playbackRates?: PlaybackRates, options?: PlaybackControlOptions}
AnySubset