我的应用程序结构如下所示:
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) {
}
}
答案 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
将被调用两次。
因为你的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仅更新更改的列表项。
这应该减少代码执行的次数。