Angular中文博客

分享让你更聪明

[译2017]提升 Angular 运行时性能的三个实际建议

浏览:115次 评论:0次 日期:2024年09月13日 10:46:36 作者:管理员

在 Oasis Digital,我们接受培训并与使用 Angular 构建软件的大公司合作。这一次,我们学到了很多有关构建性能敏感应用程序的知识。从这些经验中,我们总结出了构建出色用户体验的三大性能技巧。

什么是运行时性能?

要讨论任何类型的应用程序性能,定义我们的含义很重要。

负责创建高度响应的界面要求我们关心性能。这意味着在 17 毫秒内响应用户输入和渲染。实现这一点可以创造流畅、无缝的体验,从而增强用户对您的应用程序的信心。

我们经常编写应用程序,很少关心内部 Angular 操作。但是,有些情况需要微调并更深入地了解 Angular 的底层功能。随着数据大小或功能复杂性的增加,通常需要小心。数据表就是一个典型的例子。通常,适用于少量数据的表会随着数据大小的增加而变得滞后。

了解应用程序事件的行为方式以及它们如何与 Angular 交互是提高性能的关键。因此,Angular 中的运行时性能与其变更检测过程相关,该过程包括三个步骤:

  1. Run event handlers 运行事件处理程序
  2. Update data bindings 更新数据绑定
  3. Propagate DOM updates and repaint 传播 DOM 更新并重绘

让我们看一下可以改进每个方面的一些简单方法。

技巧 1:让活动快速进行

事件处理程序可以存在于 Angular 应用程序中的许多位置。最明显的例子是 DOM 和组件事件绑定。将这些事件设计为花费尽可能少的时间,可确保更改检测花费的时间不超过 17 毫秒。如果未达到此目标,帧速率将降至每秒 60 帧以下。发生这种情况是因为 Angular 必须等待回调完成才能继续进行更改检测并呈现更新。

让我们看一个简单的例子,其中事件绑定的执行时间可能比预期的要长。以下代码包含一个响应单击事件的组件 (AppComponent)。它通过将值从模板传递到服务 (ListService) 来实现这一点。该服务反过来利用该值来过滤列表。最后,控制权在完成之前返回到单击处理程序。直到控制权从服务返回后,更改检测才能继续。随着列表大小的增加,事件的性能将会下降。

app.component.html

<input type="text" [(ngMode)]="searchTerm">
<button (click)="update()">Search</button>
<ul>
  <li *ngFor="let instructor of instructors">
    {{ instructor }}
  </li>
</ul>

app.component.ts 

import { Component } from '@angular/core';

@Component ({
  selector: 'app-root',
  templateUrl: 'app.component.html'
})
export class AppComponent {

  public instructors = [];
  searchTerm = '';

  constructor(private ls: ListService) {
    this.instructors = ls.getList(this.searchTerm);
  }

  // Executes when user clicks the "Search" button.
  // Execution will flow into getList to perform the search.
  // The results arrive back into the update method and assigned
  // to the component.
  // All of this happens within the same change detection cycle.
  update() {
    this.instructors = this.ls.getList(this.searchTerm);
  }
}

list.service.ts 

import { Injectable } from '@angular/core';

@Injectable()
export class ListService {

  private instructorList = [
    'Paul Spears',
    'Andrew Wiens',
    'John Baur',
    'Rachel Noccioli',
    'Lance Finney',
  ];

  getList(searchTerm: string) {
    // Though this is a simple search or a small data set,
    // As the data grows in length and complexity the performance of this method
    // will likely degrade.
    return this.instructorList
      .filter(instructor => {
        return instructor === searchTerm || searchTerm === '';
      });
  }
}

如上所述,很容易忘记事件处理程序通常会运行在组件定义之外定义的代码。识别这些冗长的过程可以使它们得到改进。这通常采取选择更有效的算法的形式。或者,异步执行此过程允许在过滤完成时完成更改检测。

技巧 2:最小化变更检测

默认情况下,Angular 应用程序中的组件几乎在每次用户交互时都会进行更改检测。但是,Angular 允许您控制这个过程。您可以向 Angular 指示组件子树是否是最新的,并将其从更改检测中排除。

从更改检测中排除组件子树的第一种方法是在 @Component 装饰器中将 `changeDetection` 属性设置为 `ChangeDetectionStrategy.OnPush`。这告诉 Angular,仅需要检查输入是否已更改,并且所有输入都可以被视为不可变。

instructor-list.component.html

<ul>
  <li *ngFor="let instructor of instructors">
    {{ instructor }}
  </li>
</ul>

instructor-list.component.ts 

@Component({
  selector: 'app-instructor-list',
  templateUrl: './instructor-list.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class InstructorListComponent {
  @Input() instructors = [];
}

以增加一点复杂性为代价,可以增加控制量。例如,通过注入 ChangeDetectorRef 服务,您可以通知 Angular 组件应该与更改检测分离。然后,您可以手动控制自己调用“reattach”或“detectChanges()”,从而完全控制检查组件子树的时间和地点。

import {
  Component, Input, AfterViewInit,
  ChangeDetectionStrategy, ChangeDetectorRef
} from '@angular/core';

@Component({
  selector: 'app-instructor-list',
  templateUrl: './instructor-list.component.html',
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class InstructorListComponent implements AfterViewInit {

  // Suppose this time the instructor list doesn't change after it arrives
  @Input() instructors = [];

  constructor(private cdr: ChangeDetectorRef) { }

  // Wait until the view inits before disconnecting
  ngAfterViewInit() {
    // Since we know the list is not going to change
    // let's request that this component not undergo change detection at all
    this.cdr.detach();
  }

  // Angular provides additional controls such as the following
  // if the situation allows

  // Request a single pass of change detection for the application
  // this.cdr.markForCheck();

  // Request a single pass of change detection for just this component
  // this.cdr.detectChanges();

  // Connect this component back to the change detection process
  // this.cdr.reattach();
}

技巧 3:最小化 DOM 操作

默认情况下,当迭代对象列表时,Angular 将使用对象标识来确定是否添加、删除或重新排列项目。这对于大多数情况都很有效。然而,随着不可变实践的引入,列表内容的更改会生成新的对象。反过来,ngFor 将生成要渲染的新 DOM 元素集合。如果列表足够长或足够复杂,这将增加浏览器呈现更新所需的时间。为了缓解这个问题,可以使用 trackBy 来指示如何确定条目的更改。

instructor-list.component.html

<ul>
  <li *ngFor="let instructor of instructorList: trackBy: trackByName" >
    <span>Instructor Name {{ instructor.name }}</span>
  </li>
</ul>

instructor-list.component.ts 

import { Component } from '@angular/core';

import { ListService } from '../list.service';

@Component({
  selector: 'app-instructor-list',
  templateUrl: './instructor-list.component.html'
})
export class InstructorListComponent {
  instructorList = {name: string}[];

  constructor(private ls: ListService){
    // In this example let's assume the list service provides the
    // instructors as a realtime list of filtered instructors.
    // New updates are sent at regular intervals regardless of content change.
    // As a result the object references change with each update.
    ls.getList()
      .subscribe(list => this.instructorList = list);
  } 

  // Treat the instructor name as the unique identifier for the object
  trackByName(index, instructor) {
    return instructor.name;
  }
}

完整的运行时性能指南

面对性能问题时知道从哪里开始可能是一项艰巨的任务。希望这些实际示例能够展示性能问题可能隐藏的地方并帮助您入门。尽管这些技巧是一个很好的起点,但还有更多提高性能的机会。您可以在我们完整的Angular 运行时性能指南中阅读有关这些技巧的更多信息。

文档来源地址:https://blog.angular.dev/3-tips-for-angular-runtime-performance-from-the-real-world-d467fbc8f66e

发表评论