【问题标题】:Does TypeScript support events on classes?TypeScript 是否支持类事件?
【发布时间】:2012-10-04 13:49:49
【问题描述】:

我只是想知道在 TypeScript 中是否可以在类或接口上定义自定义事件?

这会是什么样子?

【问题讨论】:

标签: typescript


【解决方案1】:

如何将这个简化的事件用作属性?拥有类的更强类型且无继承要求:

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;
    }
}

这样使用:

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);
}

改进?

A gist for this

【讨论】:

  • 不错的解决方案。不要忘记让Event 实现IEvent。
  • 建议这样做。handlers.slice(0).forEach(h => h(data));而不是 this.handlers.forEach(h => h(data));
  • 为什么在trigger 方法中检查if (this.handlers)?不是一直都是真的吗?
  • @Tarion - 是的,在我看来,检查可以省略。我假设我在 .NET 中调用之前需要对事件处理程序进行空值检查的方式进行了一些概念性的遗留,但在我看来,处理程序成员将始终为非空值,并且它将是一个数组。如果它不是一个数组,那么代码可能无论如何都会失败。
  • number? == number | undefined 所以我可以打电话给new LiteEvent&lt;number | undefined&gt;() 对于void,我可以打电话给new LiteEvent&lt;void&gt;() 然后ev.trigger(); 就可以了
【解决方案2】:

NPM 包 Strongly Typed Events for TypeScript (GitHub) 实现了 3 种类型的事件:IEvent&lt;TSender, TArgs&gt;、ISimpleEvent&lt;TArgs&gt; 和 ISignal。这使得为​​您的项目使用正确类型的事件变得更加容易。它还从您的事件中隐藏了调度方法,就像良好的信息隐藏应该做的那样。

事件类型/接口 - 事件的定义:

interface IEventHandler<TSender, TArgs> {
    (sender: TSender, args: TArgs): void
}

interface ISimpleEventHandler<TArgs> {
    (args: TArgs): void
}

interface ISignalHandler {
    (): void;
}

示例 - 这个例子展示了如何使用时钟来实现这 3 种类型的事件:

class Clock {

    //implement events as private dispatchers:
    private _onTick = new SignalDispatcher();
    private _onSequenceTick = new SimpleEventDispatcher<number>();
    private _onClockTick = new EventDispatcher<Clock, number>();

    private _ticks: number = 0;

    constructor(public name: string, timeout: number) {
        window.setInterval( () => { 
            this.Tick(); 
        }, timeout);
    }

    private Tick(): void {
        this._ticks += 1;

        //trigger event by calling the dispatch method and provide data
        this._onTick.dispatch();
        this._onSequenceTick.dispatch(this._ticks);
        this._onClockTick.dispatch(this, this._ticks);
    }

    //expose the events through the interfaces - use the asEvent
    //method to prevent exposure of the dispatch method:
    public get onTick(): ISignal {
        return this._onTick.asEvent();
    }

    public get onSequenceTick() : ISimpleEvent<number>{
        return this._onSequenceTick.asEvent();
    }

    public get onClockTick(): IEvent<Clock, number> {
        return this._onClockTick.asEvent();
    }
}

用法 - 可以这样使用:

let clock = new Clock('Smu', 1000);

//log the ticks to the console
clock.onTick.subscribe(()=> console.log('Tick!'));

//log the sequence parameter to the console
clock.onSequenceTick.subscribe((s) => console.log(`Sequence: ${s}`));

//log the name of the clock and the tick argument to the console
clock.onClockTick.subscribe((c, n) => console.log(`${c.name} ticked ${n} times.`))

在此处阅读更多信息:On events, dispatchers and lists (a general explanation of the system)

教程
我已经写了一些关于这个主题的教程:

【讨论】:

  • 这是一个超级整洁的库。我在 Github 上阅读了有关何时使用 Signal/Simple Event/Event 的信息。
【解决方案3】:

我想你是在问一个类实例是否可以像 DOM 元素一样实现 addEventListener() 和 dispatchEvent() 。如果该类不是 DOM 节点,那么您将不得不编写自己的事件总线。您将为可以发布事件的类定义一个接口,然后在您的类中实现该接口。这是一个幼稚的例子;

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);
  }
}

【讨论】:

    【解决方案4】:

    您可以在 TypeScript 中使用自定义事件。我不确定你到底想做什么,但这里有一个例子:

    module Example {
        export class ClassWithEvents {
            public div: HTMLElement;
    
            constructor (id: string) {
                this.div = document.getElementById(id);
    
                // Create the event
                var evt = document.createEvent('Event');
                evt.initEvent('customevent', true, true);
    
                // Create a listener for the event
                var listener = function (e: Event) {
                    var element = <HTMLElement> e.target;
                    element.innerHTML = 'hello';
                }
    
                // Attach the listener to the event
                this.div.addEventListener('customevent', listener);
    
                // Trigger the event
                this.div.dispatchEvent(evt);
            }
        }
    }
    

    如果您想做更具体的事情,请告诉我。

    【讨论】:

      【解决方案5】:

      如果您希望使用标准发射器模式进行智能感知类型检查,您现在可以执行以下操作:

      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);    
      }
      

      【讨论】:

        【解决方案6】:

        你可以使用 rxjs 来实现。

        在您的班级中声明以下内容:

        export class MyClass {
            private _eventSubject = new Subject();
           
            public events = this._eventSubject.asObservable();
        
            public dispatchEvent(data: any) {
                this._eventSubject.next(data);
            }
        }
        

        然后你可以这样触发事件:

        let myClassInstance = new MyClass();
        myClassInstance.dispatchEvent(data);
        

        并通过以下方式收听此事件:

        myClassInstance.events.subscribe((data: any) => { yourCallback(); });
        

        【讨论】:

          【解决方案7】:

          此解决方案允许您直接在函数调用中编写参数,而不需要将所有参数包装在一个对象中。

          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
              }
          
          }
          

          用法:

          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");
          

          【讨论】:

          • 不解释就投反对票的用处不大
          【解决方案8】:

          这是一个使用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);
              }
          }
          

          然后任何客户端都可以订阅接收这些事件:

          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
          });
          

          当您不再需要这些事件时,您可以取消订阅:

          sub1.cancel();
          
          sub2.cancel();
          

          【讨论】:

            【解决方案9】:

            您可以在YouTube 找到事件调度程序声明。观看视频后,您将能够拥有事件调度程序的完全类型化版本

            【讨论】:

              猜你喜欢
              • 2017-06-26
              • 2016-08-20
              • 1970-01-01
              • 2019-08-13
              • 1970-01-01
              • 2011-10-22
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多