interface IListenerRegistration { target : EventTarget ; type : string ; listener : EventListener ; } export interface IView < TInit , TProps > { attach ( parent , init ) : void ; update ( props ) : void ; detach ( ) : void ; } export abstract class View < TInit extends TProps , TProps = { } | undefined > implements IView < TInit , TProps > { private _root ? : Element ; private listeners ? : IListenerRegistration [ ] ; public attach ( parent , init ) { this . _root = this . onAttach ( init ) ; this . onUpdate ( init ) ; console . assert ( parent . hasChildNodes ( ) === false ) ; parent . append ( this . _root ) ; } public update ( props ) { this . onUpdate ( props ) ; } public detach ( ) { const { root : root , listeners } = this ; const parent = root . parentNode ; root . remove ( ) ; console . assert ( parent . hasChildNodes ( ) === false ) ; if ( listeners !== undefined ) { for ( const { target , type , listener } of listeners ) { target . removeEventListener ( type , listener ) ; } } this . onDetach ( ) ; this . _root = undefined ; this . listeners = undefined ; } protected get root ( ) { return this . _root ; } protected abstract onAttach ( init ) ; protected abstract onUpdate ( props ) ; protected abstract onDetach ( ) ; protected onDom < K extends keyof HTMLElementEventMap > ( target , type , listener ) { const eventListener = listener as EventListener ; const registration = { target , type , listener : eventListener } ; const listeners = this . listeners ; if ( listeners === undefined ) { this . listeners = [ registration ] ; } else { listeners . push ( registration ) ; } target . addEventListener ( type , eventListener ) ; } }