Does TypeScript support events on classes?

后端 未结 8 734
小鲜肉
小鲜肉 2021-01-30 01:53

I am just wondering if in TypeScript you can define custom events on your classes or interfaces?

What would this look like?

相关标签:
8条回答
  • 2021-01-30 02:33

    How about this simplified event to be used as a property? Stronger typing of the owning class and no inheritance requirement:

    interface ILiteEvent<T> {
        on(handler: { (data?: T): void }) : void;
        off(handler: { (data?: T): void }) : void;
    }
    
    class LiteEvent<T> implements ILiteEvent<T> {
        private handlers: { (data?: T): void; }[] = [];
    
        public on(handler: { (data?: T): void }) : void {
            this.handlers.push(handler);
        }
    
        public off(handler: { (data?: T): void }) : void {
            this.handlers = this.handlers.filter(h => h !== handler);
        }
    
        public trigger(data?: T) {
            this.handlers.slice(0).forEach(h => h(data));
        }
    
        public expose() : ILiteEvent<T> {
            return this;
        }
    }
    

    used like so:

    class Security{
        private readonly onLogin = new LiteEvent<string>();
        private readonly onLogout = new LiteEvent<void>();
    
        public get LoggedIn() { return this.onLogin.expose(); } 
        public get LoggedOut() { return this.onLogout.expose(); }
    
        // ... onLogin.trigger('bob');
    }
    
    function Init() {
        var security = new Security();
    
        var loggedOut = () => { /* ... */ }
    
        security.LoggedIn.on((username?) => { /* ... */ });
        security.LoggedOut.on(loggedOut);
    
        // ...
    
        security.LoggedOut.off(loggedOut);
    }
    

    Improvements?

    A gist for this

    0 讨论(0)
  • 2021-01-30 02:33

    I think you are asking if a class instance can implement addEventListener() and dispatchEvent() like a DOM element. If the class is not a DOM node, then you would have to write your own event bus. You would define an interface for a class that can publish events, then implement the interface in the your classes. Here is a naive example;

    interface IEventDispatcher{
      // maintain a list of listeners
      addEventListener(theEvent:string, theHandler:any);
    
      // remove a listener
      removeEventListener(theEvent:string, theHandler:any);
    
      // remove all listeners
      removeAllListeners(theEvent:string);
    
      // dispatch event to all listeners
      dispatchAll(theEvent:string);
    
      // send event to a handler
      dispatchEvent(theEvent:string, theHandler:any);
    }
    
    class EventDispatcher implement IEventDispatcher {
      private _eventHandlers = {};
    
      // maintain a list of listeners
      public addEventListener(theEvent:string, theHandler:any) {
        this._eventHandlers[theEvent] = this._eventHandlers[theEvent] || [];
        this._eventHandlers[theEvent].push(theHandler);
      }
    
      // remove a listener
      removeEventListener(theEvent:string, theHandler:any) {
        // TODO
      }
    
      // remove all listeners
      removeAllListeners(theEvent:string) {
        // TODO
      }
    
      // dispatch event to all listeners
      dispatchAll(theEvent:string) {
        var theHandlers = this._eventHandlers[theEvent];
        if(theHandlers) {
          for(var i = 0; i < theHandlers.length; i += 1) {
            dispatchEvent(theEvent, theHandlers[i]);
          }
        }
      }
    
      // send event to a handler
      dispatchEvent(theEvent:string, theHandler:any) {
        theHandler(theEvent);
      }
    }
    
    0 讨论(0)
  • 2021-01-30 02:33

    If you are looking to get intelli-sense type checking using the standard emitter pattern you can now do the following:

    type DataEventType = "data";
    type ErrorEventType = "error";
    declare interface IDataStore<TResponse> extends Emitter {
        on(name: DataEventType, handler : (data: TResponse) => void);   
        on(name: ErrorEventType, handler: (error: any) => void);    
    }
    
    0 讨论(0)
  • 2021-01-30 02:33

    This solution allows you to directly write the parameters in the function call instead of needing to wrap all your parameters in an object.

    interface ISubscription {
       (...args: any[]): void;
    }
    
    class PubSub<T extends ISubscription> {
        protected _subscribed : ISubscriptionItem[] = [];
    
        protected findSubscription(event : T) : ISubscriptionItem {
            this._subscribed.forEach( (item : ISubscriptionItem) =>{
                if (item.func==event)
                  return item;
            } );
            return null;
        }
    
        public sub(applyObject : any,event : T) {
            var newItem = this.findSubscription(event);
            if (!newItem) {
                newItem = {object : applyObject, func : event };
                this._subscribed.push(newItem);
                this.doChangedEvent();
            }
        }
        public unsub(event : T) {
            for ( var i=this._subscribed.length-1 ; i>=0; i--) {
                if (this._subscribed[i].func==event)
                    this._subscribed.splice(i,1);
            }
            this.doChangedEvent();
        }
        protected doPub(...args: any[]) {
            this._subscribed.forEach((item : ISubscriptionItem)=> {
                item.func.apply(item.object, args);
            })
        }
    
        public get pub() : T {
            var pubsub=this;
            var func=  (...args: any[]) => {
                pubsub.doPub(args);
            }
            return <T>func;
        }
    
        public get pubAsync() : T {
            var pubsub=this;
            var func =  (...args: any[]) => {
                setTimeout( () => {
                    pubsub.doPub(args);
                });
            }
            return <T>func;
        }
    
        public get count() : number {
            return this._subscribed.length
        }
    
    }
    

    Usage:

    interface ITestEvent {
        (test : string): void;
    }
    
    var onTestEvent = new PubSub<ITestEvent>();
    //subscribe to the event
    onTestEvent.sub(monitor,(test : string) => {alert("called:"+test)});
    //call the event
    onTestEvent.pub("test1");
    
    0 讨论(0)
  • 2021-01-30 02:39

    You can find an event dispatcher declaration at YouTube. Following the video you will be able to have a fully typed version of the event dispatcher

    0 讨论(0)
  • 2021-01-30 02:52

    Here's a simple example of adding custom-type events to your class, using sub-events:

    class MyClass {
    
        readonly onMessage: SubEvent<string> = new SubEvent();
        readonly onData: SubEvent<MyCustomType> = new SubEvent();
    
        sendMessage(msg: string) {
            this.onMessage.emit(msg);
        }
    
        sendData(data: MyCustomType) {
            this.onData.emit(data);
        }
    }
    

    And then any client can subscribe to receive those events:

    const a = new MyClass();
    
    const sub1 = a.onMessage.subscribe(msg => {
        // msg here is strongly-typed
    });
    
    const sub2 = a.onData.subscribe(data => {
        // data here is strongly-typed
    });
    

    And when you no longer need the events, you can cancel the subscriptions:

    sub1.cancel();
    
    sub2.cancel();
    
    0 讨论(0)
提交回复
热议问题