From adc7f4d3f4c73106573455676f3cdd9747fbe62e Mon Sep 17 00:00:00 2001 From: sinwoojin Date: Mon, 9 Feb 2026 16:38:29 +0900 Subject: [PATCH] =?UTF-8?q?fix:=20Base=20null=20safety=20=EB=B0=8F=20fluen?= =?UTF-8?q?t=20API=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/rune/src/Base.ts | 165 +++++++++++--------------------------- 1 file changed, 49 insertions(+), 116 deletions(-) diff --git a/packages/rune/src/Base.ts b/packages/rune/src/Base.ts index 72b69f7..db59cc4 100644 --- a/packages/rune/src/Base.ts +++ b/packages/rune/src/Base.ts @@ -1,152 +1,106 @@ -import { eventHelper } from './EventHelper'; -import { rune } from './rune'; import type { CustomEventInitFromClass } from './CustomEventWithDetail'; +import { eventHelper } from './EventHelper'; import { _camelToColonSeparated } from './lib/_camelToColonSeparated'; +import { rune } from './rune'; export abstract class Base { - protected _element: HTMLElement | null = null; - private _runeElNumber = 0; - private _isTempElId = false; + protected _element?: HTMLElement; + private _tempElId?: string; protected onRender() {} + protected onMount() {} + protected onUnmount() {} protected _onRender(): this { - rune.set(this.element(), this); - eventHelper.addDecoratedListeners(this, this.element()); + const el = this.element(); + rune.set(el, this); + eventHelper.addDecoratedListeners(this, el); this.onRender(); - eventHelper.addReservedListener(this, this.element()); + eventHelper.addReservedListener(this, el); return this; } - protected onMount() {} - protected _onMount(): this { this.onMount(); return this; } - protected onUnmount() {} - - protected _onUnmount() { + protected _onUnmount(): this { this.onUnmount(); return this; } + protected _setElement(element: HTMLElement): this { + this._element = element; + return this; + } + element(): HTMLElement { - if (this._element === null) { + if (!this._element) { throw new TypeError("element is not created. call 'view.render' or 'view.hydrateFromSSR'."); } return this._element; } isRendered(): boolean { - return this._element !== null; + return !!this._element; } - chain(f: (this: this, view: this) => void): this { - f.call(this, this); + chain(f: (view: this) => void): this { + f(this); return this; } - async chainAsync(f: (this: this, view: this) => Promise): Promise { - await f.call(this, this); + async chainAsync(f: (view: this) => Promise): Promise { + await f(this); return this; } - safely(f: (this: this, view: this) => void): this { + safely(f: (view: this) => void): this { return this.isRendered() ? this.chain(f) : this; } - async safelyAsync(f: (this: this, view: this) => Promise): Promise { + async safelyAsync(f: (view: this) => Promise): Promise { return this.isRendered() ? this.chainAsync(f) : this; } - protected _getElId() { - if (this.element().id) { - return this.element().id; - } else { - this._isTempElId = true; - return (this.element().id = `rune-temp-id-${++this._runeElNumber}`); + protected _getElId(): string { + const el = this.element(); + if (el.id) return el.id; + + if (!this._tempElId) { + this._tempElId = `rune-temp-id-${crypto.randomUUID()}`; + el.id = this._tempElId; } + return el.id; } protected _removeTempElId(): this { - if (this._isTempElId) { - this.element().removeAttribute('id'); - this._isTempElId = false; + if (this._tempElId && this._element?.id === this._tempElId) { + this._element.removeAttribute('id'); } + this._tempElId = undefined; return this; } - protected _setElement(element: HTMLElement): this { - this._element = element; - return this; - } - - /* eslint-disable @typescript-eslint/no-explicit-any */ - addEventListener Event>( - eventType: T, - listener: (this: this, ev: InstanceType) => any, - options?: boolean | AddEventListenerOptions, - ): this; addEventListener( eventType: K, - listener: (this: this, ev: HTMLElementEventMap[K]) => any, - options?: boolean | AddEventListenerOptions, - ): this; - addEventListener( - eventType: string, - listener: (this: this, ev: Event) => any, + listener: (this: this, ev: HTMLElementEventMap[K]) => void, options?: boolean | AddEventListenerOptions, - ): this; - addEventListener(eventType: any, listener: any, options?: any): this { - eventHelper.addEventListener(this, this._element, eventType, listener, options); + ): this { + eventHelper.addEventListener(this, this.element(), eventType, listener, options); return this; } - removeEventListener Event>( - eventType: T, - listener: (this: this, ev: InstanceType) => any, - options?: boolean | AddEventListenerOptions, - ): this; removeEventListener( eventType: K, - listener: (this: this, ev: HTMLElementEventMap[K]) => any, + listener: (this: this, ev: HTMLElementEventMap[K]) => void, options?: boolean | EventListenerOptions, - ): this; - removeEventListener( - eventType: string, - listener: (this: this, ev: Event) => any, - options?: boolean | EventListenerOptions, - ): this; - removeEventListener(eventType: any, listener: any, options?: any): this { - eventHelper.removeEventListener(this, this._element, eventType, listener, options); + ): this { + eventHelper.removeEventListener(this, this.element(), eventType, listener, options); return this; } - delegate Event, T extends new (...args: any[]) => Base>( - eventClass: K, - View: T, - listener: ( - this: this, - e: InstanceType & { originalEvent: InstanceType }, - targetView: InstanceType, - ) => void, - ): this; - delegate Event>( - eventClass: K, - selector: string, - listener: (this: this, e: InstanceType & { originalEvent: InstanceType }) => void, - ): this; - delegate Base>( - eventType: K, - View: T, - listener: ( - this: this, - e: HTMLElementEventMap[K] & { originalEvent: HTMLElementEventMap[K] }, - targetView: InstanceType, - ) => void, - ): this; delegate( eventType: K, selector: string, @@ -154,29 +108,12 @@ export abstract class Base { this: this, e: HTMLElementEventMap[K] & { originalEvent: HTMLElementEventMap[K] }, ) => void, - ): this; - delegate Base>( - eventType: string, - View: T, - listener: ( - this: this, - e: Event & { originalEvent: Event }, - targetView: InstanceType, - ) => void, - ): this; - delegate( - eventType: string, - selector: string, - listener: (this: this, e: Event & { originalEvent: Event }) => any, - ): this; - delegate(eventType: any, selector: any, listener: any): this { - eventHelper.delegate(this, this._element, eventType, selector, listener); + ): this { + eventHelper.delegate(this, this.element(), eventType, selector, listener); return this; } - /* eslint-enable @typescript-eslint/no-explicit-any */ - - private _createEvent< + private _createCustomEvent< T extends new (...args: any[]) => Event, U extends CustomEventInitFromClass, >(EventClass: T, options: U): InstanceType { @@ -188,15 +125,11 @@ export abstract class Base { event: T, eventInitDict: U, ): this; - dispatchEvent Event, U extends CustomEventInitFromClass>( - event: Event | T, - eventInitDict?: U, - ): this { - if (event instanceof Event) { - this.element().dispatchEvent(event); - } else { - this.element().dispatchEvent(this._createEvent(event, eventInitDict!)); - } + dispatchEvent(event: any, eventInitDict?: any): this { + const el = this.element(); + el.dispatchEvent( + event instanceof Event ? event : this._createCustomEvent(event, eventInitDict), + ); return this; }