我想创建一个将 自定义 HTML 元素与一些附加属性和方法混合在一起的类:如果我扩展该类以创建新组件,则新组件将是具有附加属性/方法的自定义 HTML 元素。
我尝试添加的一个功能是定义和分派自定义事件的帮助程序。理想情况下,每个组件都会有一个
events
对象来定义每个事件,包括:
然后,当发生某些交互时,我们可以
emit
事件并使用正确的数据分派它。
例如,假设我们正在创建一个店面,人们可以在其中购买烘焙食品:
export function ComponentFactory(BaseHTMLElement: typeof HTMLElement) {
return class Component extends BaseHTMLElement {
events = {} as Record<string, (...args: Array<any>) => any>; // eslint-disable-line @typescript-eslint/no-explicit-any
emit<
EventKey extends keyof this[`events`],
EventDetail extends this[`events`][EventKey],
>(eventKey: EventKey, ...args: Parameters<EventDetail>) {
const eventName = eventKey as string;
const getDetail = this.events[eventName] as EventDetail;
const detail = getDetail(...args) as ReturnType<EventDetail>;
this.dispatchEvent(new CustomEvent(eventName, {
bubbles: true,
detail,
}));
}
};
}
export class BakeryItem extends ComponentFactory(HTMLDivElement) {
events = {
buy: (quantity: number) => quantity,
buyDozen: (dozens: number) => dozens * 12,
};
onBuyClick(quantity: number) {
this.emit(`buy`, quantity);
}
onBuyDozenClick(quantity: number) {
this.emit(`buyDozen`, quantity);
}
}
Typescript 在调用
this.emit
时抛出错误。例如,调用this.emit('buy', quantity)
时:
Argument of type '[number]' is not assignable to parameter of type 'Parameters<this["events"]["buy"]>'.
但是,当我调用
new BakeryItem().emit('buy', 3)
时,它会正确推断所有类型并且不会引发错误。
如何正确输入此内容以便可以同时调用
bakeryItem.emit
和 new BakeryItem().emit
?
设法让 ComponentFactory 接受泛型作为您的
events
对象的类型,然后使用它在发射函数中构造您的泛型类型来使其工作。当我在这里进行初始测试时,似乎 Parameters
泛型确实不喜欢纯数组访问中的值(例如 Parameters<(typeof this)["events"]["buy"]>
而不是 Parameters<typeof this.events.buy>
)。下面是我如何设法让它工作,但它确实需要添加一个参数来对您的 ComponentFactory
函数进行类型推断:
export function ComponentFactory<
T extends Record<string, (...args: any[]) => any>
>(BaseHTMLElement: typeof HTMLElement, events: T) {
return class Component extends BaseHTMLElement {
events = events;
emit<
EventKey extends keyof typeof this.events,
EventDetail extends (typeof this.events)[EventKey]
>(eventKey: EventKey, ...args: Parameters<(typeof this.events)[EventKey]>) {
const eventName = eventKey as string;
const getDetail = this.events[eventName] as EventDetail;
const detail = getDetail(...args) as ReturnType<EventDetail>;
this.dispatchEvent(
new CustomEvent(eventName, {
bubbles: true,
detail,
})
);
}
};
}
export class BakeryItem extends ComponentFactory(HTMLDivElement, {
buy: (quantity: number) => quantity,
buyDozen: (dozens: number) => dozens * 12,
}) {
onBuyClick(quantity: number) {
this.emit("buy", quantity);
}
onBuyDozenClick(quantity: number) {
this.emit("buyDozen", quantity);
}
}