Angular2 中的事件委派

Event delegation in Angular2

本文关键字:委派 事件 Angular2      更新时间:2023-09-26

我正在用ng2开发一个应用程序,但我正在努力解决一些问题。我正在构建一个日历,您可以在其中选择一个日期范围,我需要对日单元格上的clickmouseenter/mouseleave事件做出反应。所以我有一个这样的代码(简化):

日历组件.html

<month>
    <day *ngFor="let day of days" (click)="handleClick()" 
         (mouseenter)="handleMouseEnter()" 
         (mouseleave)="handleMouseLeave()" 
         [innerHTML]="day"></day>
</month>

但这在浏览器的内存中给了我数百个单独的事件侦听器(每天的单元格有 3 个事件侦听器,我一次最多可以显示 12 个月,因此它将超过 1k 个侦听器)。

所以我想用"正确的方式"来做,使用称为"事件委托"的方法。我的意思是,在父组件(month)上附加一个单击事件,当它收到单击事件时,只需检查它是否发生在Day组件上 - 只有这样我才会对此单击做出反应。类似于 jQuery 在它的 on() 方法中执行的操作,当你传递给它 selector 参数时。

但我是通过在处理程序的代码中原生引用 DOM 元素来做到这一点的:

month.component.ts

private handleClick(event) {
    if (event.target.tagName === 'DAY') {
        // handle day click
    } else {
        // handle other cases
    }
}

我的同事拒绝了我的想法,因为 - 正如他们所说 - "NG2 中必须有一种更简单、更合适的方法来处理这个问题;就像在jQuery中一样。此外,它在这里失控了 - 你对 Day 在 Month 代码中的点击做出反应。

所以,我的问题是,有没有更好的方法?还是我试图解决一个我不应该再费心解决的问题,因为用户的设备每天都在获得越来越多的内存/处理能力?

提前感谢!

简介

今天偶然发现了这个,我真的可以看到在很多应用程序中对这种实现的需求。现在我不能保证这是 100% 最好的技术,但是我已经竭尽全力使这种方法尽可能有棱角。

我想出的方法有两个阶段。阶段 1 和阶段 2 将总共增加 years * months + years * months * days ,因此在 1 年内您将有 12 + 365 个事件。


阶段范围

第 1 阶段:将事件从单击一个月时委派到单击的实际日期,而无需当天的事件。
第 2 阶段:将所选日期传播回月份。

在深入研究之前,应用程序由 3 个组件组成,这些组件按以下顺序嵌套:app => month => day


这是所有必需的html.app.component托管月份,month.component托管天数,day.component除了将其日期显示为文本外,什么都不做。

app.component.html

<app-month *ngFor="let month of months" [data-month]="month"></app-month>

月.组件.html

<app-day *ngFor="let day of days" [data-day]="day">{{day}}</app-day>

天组件.html

<ng-content></ng-content>

这是相当股票标准的东西。


第 1 阶段

让我们看一下要从哪个month.component.ts委派我们的活动。

// obtain a reference to the month(this) element 
constructor(private element: ElementRef) { }
// when this component is clicked...
@HostListener('click', ['$event'])
public onMonthClick(event) {
  // check to see whether the target element was a child or if it was in-fact this element
  if (event.target != this.element.nativeElement) {
    // if it was a child, then delegate our event to it.
    // this is a little bit of javascript trickery where we are going to dispatch a custom event named 'delegateclick' on the target.
    event.target.dispatchEvent(new CustomEvent('delegateEvent'));
  }
}

在阶段 1 和阶段 2 中,只有 1 个警告,那就是; 如果你的day.component.html中有嵌套的子元素,你将需要为此实现冒泡,在 if 语句中实现更好的逻辑,或者快速破解将是......在day.component.css :host *{pointer-events: none;}


现在我们需要告诉我们的 day.component 期待我们的delegateEvent事件。因此,day.component.ts您所要做的就是(以最有棱角的方式)是......
@HostListener('delegateEvent', ['$event'])
 public onEvent() {
   console.log("i've been clicked via a delegate!");
 }

这是有效的,因为打字稿不关心事件是否是本机的,它只会将一个新的 javascript 事件绑定到元素,从而允许我们通过 event.target.dispatchEvent "本机"调用它,就像我们在上面month.component.ts中所做的那样。

第一阶段到此结束,我们现在成功地将事件从我们的月份委派到我们的天。


第 2 阶段

那么,如果我们说想要在 day.component 中的委托事件中运行一点逻辑,然后将其返回给 month.component - 以便它可以以一种非常面向对象的方法继续使用自己的功能,会发生什么?幸运的是,我们可以很容易地实现这一点!

month.component.ts更新以下内容。所有改变的是,我们现在将通过事件调用传递一个函数,并且我们定义了回调函数。

@HostListener('click', ['$event'])
  public onMonthClick(event) {  
    if (event.target != this.element.nativeElement) {
      event.target.dispatchEvent(new CustomEvent('delegateEvent', { detail: this.eventDelegateCallback}));
    }
  }
  public eventDelegateCallback(data) {
    console.log(data);
  }

剩下的就是在day.component.ts内调用这个函数......

public onEvent(event) {
    // run whatever logic you like, 
    //return whatever data you like to month.component
    event.detail(this.day);
}

不幸的是,我们的回调函数在这里命名有点模棱两可,但是打字稿会抱怨该属性不是CustomEventInit定义的对象文字,如果以其他方式命名。


多事件漏斗

这种方法的另一个很酷的事情是,您永远不必定义超过此数量的事件,因为您可以通过此委托汇集所有事件,然后在day.component.ts中运行逻辑以按event.type过滤......

month.component.ts

@HostListener('click', ['$event'])
@HostListener('mouseover', ['$event'])
@HostListener('mouseout', ['$event'])
  public onMonthEvent(event) {  
    if (event.target != this.element.nativeElement) {
      event.target.dispatchEvent(new CustomEvent('delegateEvent', { detail: this.eventDelegateCallback }));
    }
  }

day.component.ts

private eventDelegateCallback: any;
@HostListener('delegateEvent', ['$event'])
  public onEvent(event) {
    this.eventDelegateCallback = event.detail;
    if(event.type == "click"){
       // run click stuff
       this.eventDelegateCallback(this.day)
    }
  }