为什么angular2会多次执行方法?

时间:2016-12-16 15:32:38

标签: angular lifecycle

我的应用程序结构如下所示:

enter image description here

TS:

...
export class TodoListComponent {

    get sortedTodos():ITodo[] {
            console.log(this.counter++);
            ...
        } 
    ....

HTML:

  <div class="todo-item" *ngFor="let todo of sortedTodos" [class.completed]="todo.completed">
        <todo-list-item [todo]="todo" class="todo-item" (deleted)="onTodoDeleted(todo)"
                        (toggled)="onTodoUpdated($event)"></todo-list-item>
    </div>

如果我启动应用程序,我会在控制台中看到:

1
2
3
4
5
6

我对此行为感到困惑。对我来说,它看起来很奇怪,我认为它可能导致错误和性能问题。请解释为什么我一次加载页面时执行 6!次。

我不确定我是否在主题中提供了所有必需的信息。随意请求其他东西。此外,所有代码库都可以找到bitbucket repo link

P.S。

完整的ts文件内容:

import {Component, Input, Output, EventEmitter} from "@angular/core"

import {ITodo} from "../../shared/todo.model";
import {TodoService} from "../../shared/todoService";

@Component({
    moduleId: module.id,
    selector: "todo-list",
    templateUrl: "todo-list.component.html",
    styleUrls: ["todo-list.component.css"],
})
export class TodoListComponent {
    @Input() todos:ITodo[];

    @Output() updated:EventEmitter<ITodo> = new EventEmitter<ITodo>();
    @Output() deleted:EventEmitter<ITodo> = new EventEmitter<ITodo>();

    get sortedTodos():ITodo[] {
        return !this.todos ? [] :
            this.todos.map((todo:ITodo)=>todo)
                .sort((a:ITodo, b:ITodo)=> {
                    if (a.title > b.title) {
                        return 1;
                    } else if (a.title < b.title) {
                        return -1;
                    }
                    return 0;
                })
                .sort((a:ITodo, b:ITodo)=> (+a.completed - (+b.completed)));
    }

    onTodoDeleted(todo:ITodo):void {
        this.deleted.emit(todo);
    }

    onTodoUpdated(todo:ITodo):void {
        this.updated.emit(todo);
    }

    constructor(private todoService:TodoService) {
    }
}

4 个答案:

答案 0 :(得分:5)

它执行了6次,因为:

tick类中有一个ApplicationRef方法,执行3次2次更改检测周期。如果您通过调用enableProdMode()启用生产模式,它将被执行3次

ApplicationRef是对页面上运行的Angular应用程序的引用。 tick方法正在运行从根到叶的更改检测。

以下是tick方法的外观(https://github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/application_ref.ts#L493-L509):

tick(): void {
  if (this._runningTick) {
    throw new Error('ApplicationRef.tick is called recursively');
  }

  const scope = ApplicationRef_._tickScope();
  try {
    this._runningTick = true;
    this._views.forEach((view) => view.ref.detectChanges()); // check
    if (this._enforceNoNewChanges) {
      this._views.forEach((view) => view.ref.checkNoChanges()); // check only for debug mode
    }
  } finally {
      this._runningTick = false;
      wtfLeave(scope);
  }
}

对于调试模式 tick启动两次更改检测周期。因此,编译视图中的detectChangesInternal将被调用两次。

enter image description here

因为你的sortedTodos属性是一个getter所以它每次都会作为一个函数执行。

在此处详细了解(Change Detection in Angular 2

那么我们知道我们的sortedTodos getter被调用两次tick

为什么tick方法执行了3次?

1)首先tick由引导应用程序手动运行。

private _loadComponent(componentRef: ComponentRef<any>): void {
  this.attachView(componentRef.hostView);
  this.tick();

https://github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/application_ref.ts#L479

2) Angular2在zonejs中运行,因此它是管理变更检测的主要内容。上面提到的ApplicationRef订阅zone.onMicrotaskEmpty

this._zone.onMicrotaskEmpty.subscribe(
  {next: () => { this._zone.run(() => { this.tick(); }); }});

https://github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/application_ref.ts#L433

区域 稳定时,

onMicrotaskEmpty 事件是一个指标

private checkStable() {
  if (this._nesting == 0 && !this._hasPendingMicrotasks && !this._isStable) {
    try {
      this._nesting++;
      this._onMicrotaskEmpty.emit(null); // notice this
    } finally {
      this._nesting--;
      if (!this._hasPendingMicrotasks) {
        try {
          this.runOutsideAngular(() => this._onStable.emit(null));
        } finally {
          this._isStable = true;
        }
      }
    }
  }
}

https://github.com/angular/angular/blob/2.3.0/modules/%40angular/core/src/zone/ng_zone.ts#L195-L211

因此,在一些zonejs任务之后发出此事件

3)您正在使用angular2-in-memory-web-api软件包,当您尝试获取模拟数据时,它会执行以下操作:

createConnection(req: Request): Connection {
    let res = this.handleRequest(req);

    let response = new Observable<Response>((responseObserver: Observer<Response>) => {
      if (isSuccess(res.status)) {
        responseObserver.next(res);
        responseObserver.complete();
      } else {
        responseObserver.error(res);
      }
      return () => { }; // unsubscribe function
    });

    response = response.delay(this.config.delay || 500); // notice this
    return {
      readyState: ReadyState.Done,
      request: req,
      response
    };
}

https://github.com/angular/in-memory-web-api/blob/0.0.20/src/in-memory-backend.service.ts#L136-L155

它启动了常规的zonejs任务循环,它创建了区域unStable,最后在执行任务后执行了onMicrotaskEmpty事件再次发送

您可以在此处找到有关zonejs的更多详细信息

<强>小结

因此,您可以看到您的解决方案有点不对劲。您不应该在模板中使用getter或function作为绑定。您可以在这里找到可能的解决方案

答案 1 :(得分:1)

这就是Template expressions在Angular 2中的工作方式,Angular比我们想象的更频繁地执行模板表达。可以在每次按键或鼠标移动后调用它们。

Expression guidelines ,以便没有错误或性能问题。

  

模板表达式可以创建或中断应用程序。请关注   这些准则:

     

在   只有这些指南的例外情况才具体   你完全理解的情况。

     

没有可见的副作用

     

模板表达式不应更改其他任何应用程序状态   而不是目标财产的价值。

     

此规则对Angular的“单向数据流”策略至关重要。   我们永远不应该担心读取组件值可能会改变一些   其他显示值。整个视图应该是稳定的   渲染传递。

     

快速执行

     

Angular比我们想象的更频繁地执行模板表达式。他们   每次按键或鼠标移动后都可以调用。表达式应该   快速完成或用户体验可能会拖累,尤其是速度较慢   设备。考虑缓存从其他值计算的值   计算很昂贵。

     

<强> SIMPLICITY

     

虽然可以编写非常复杂的模板表达式,但是我们   真的不应该。

     

属性名称或方法调用应该是常态。偶尔   布尔否定(!)没问题。否则,限制应用程序和   组件本身的业务逻辑,它将更容易   开发和测试。

     

<强>幂等性

     

幂等表达式是理想的,因为它没有副作用   并提高Angular的变化检测性能。

     

在Angular术语中,幂等表达式总是返回   同样的事情,直到其一个依赖值发生变化。

     

在单次转弯期间,相关值不应更改   环。如果幂等表达式返回字符串或数字,则为   连续两次调用时返回相同的字符串或数字。如果   表达式返回一个对象(包括一个Array),它返回相同的内容   连续两次调用时的对象引用。

您的案例中的数字 6 取决于您的HTML模板中有多少表达式。

希望这会有所帮助!!

答案 2 :(得分:0)

  

NgFor指令从迭代中为每个项实例化一次模板。每个实例化模板的上下文都从外部上下文继承,给定的循环变量设置为可迭代的当前项。

我认为ITodo[]返回的getSortedTodos()中有6个项目,*ngFor="let todo of sortedTodos"中的每个循环,get中的TodoListComponent被调用一次这就是为什么你看到打印了六个数字的原因。

来源: https://angular.io/docs/ts/latest/api/common/index/NgFor-directive.html

答案 3 :(得分:0)

你做错了..

*ngFor="let todo of sortedTodos"

这不是智能代码。如果一个待办事项发生变化,那么它将重新渲染整个列表。

您需要使用trackBy。这介绍了情报。使用trackBy时,Angular2仅更新更改的列表项。

这应该减少代码执行的次数。