![cc5af2ef535b9c702d53161a4d38ac45.png](https://img-blog.csdnimg.cn/img_convert/cc5af2ef535b9c702d53161a4d38ac45.png)
开发实践经验告诉我们,在开发过程中,获取成就感最高或引以为豪的不是你完成了某个需求,而是在实际的重构过程中提升可观测到的应用性能优化。
在SPA应用开发中,首当其冲当属减少首屏渲染时间,这就不得最小化资源请求的大小。比如icon font化、图片资源使用webp格式、资源服务端gzip压缩这是必要的,尽量最少化http请求等等。上述这些提到的优化是必要及实际应用性很高的一些手段,当然还有从应用本身来触发的,那就是应用运行时性能提升。比如:常用比较费performance的函数memorize化、数据请求缓存等这些也是提升体验及性能的基本手段。
下面,我们从一个比较简单的例子来讲述onPush的优化体现:
- 一个简单的应用:
![7553110c3a1673d2aeca1025348d741f.gif](https://img-blog.csdnimg.cn/img_convert/7553110c3a1673d2aeca1025348d741f.gif)
就是一个展示的组件接收两个参数,departmentName和employeeList。负责展示数据组件中有个button按钮,用来删除组内的成员;同时有一个输入框,用于添加成员。所以组件中会有event向外输出,一个是remove事件,一个是add事件。
那么可以这样设计:
import { Component, Input, Output, EventEmitter } from "@angular/core";
import { Employee } from './Employee';
const fibonacci = (num: number): number => {
if (num === 1 || num === 2) {
return 1;
}
return fibonacci(num - 1) + fibonacci(num - 2);
}
@Component({
selector: 'employee-list',
templateUrl: './Employee.component.html',
styleUrls: ['./Employee.component.scss']
})
export class EmployeeListComponent {
@Input() departmentName: string
@Input() employeeList: Employee[]
@Output() remove = new EventEmitter<Employee>()
@Output() add = new EventEmitter<string>()
name: string
handleKey (event: any): void {
if (event.keyCode === 13) {
this.add.emit(this.name)
this.name = ''
}
}
handleRemove (employee: Employee): void {
this.remove.emit(employee)
}
calculate (num: number) {
return fibonacci(num)
}
}
其中template是这样的:
<div class="employee-container">
<h1>{{ departmentName }}</h1>
<p>
<label for="employee"></label>
<input type="text" id="employee" placeholder="please input a employee name" [(ngModel)]="name" (keydown)="handleKey($event)" />
</p>
<ul>
<li *ngFor="let employee of employeeList">
<span>{{ employee.name }}</span>
<span class="rate">{{ calculate(employee.rate) }}</span>
<button (click)="handleRemove(employee)">delete</button>
</li>
</ul>
</div>
但是在展示的数据中,会有一个比较复杂的计算,那就是employee的投票rate,是一个斐波那契数计算,具体在代码中的fibonacci函数。在这里例子中,引入斐波那契数计算用以代替在实际业务开发过程中的逻辑或业务计算,这也是我们接下来实现优化的目的。首先打开devtools的performance栏目,我们分析下:
![dce101526e8efef56a3b5466678fb802.png](https://img-blog.csdnimg.cn/img_convert/dce101526e8efef56a3b5466678fb802.png)
可以看出,当我们在添加一个成员的时候,基本上最耗时间的是我们组件内部声明的fibonacci,同时当我们在calculate函数中添加一段console.log('in')会发现:
![6854c1ca70f43fc45b25a65e6af4783b.gif](https://img-blog.csdnimg.cn/img_convert/6854c1ca70f43fc45b25a65e6af4783b.gif)
通过上面的gif图我们可以看出,这个calculate函数基本上会执行多次,实际上情况是在我们每次数一个字母的时候都会执行72次,同时包括相关的鼠标事件,比如在输入的时候mousedown和mouseup事件。那么为什么会出现这么多次不必要的计算呢?这是因为在没有指定任何检测机制的时候,Angular默认的change detection一旦触发,它会重新计算模板中以及绑定的表达式,然后再执行相应的视图更新。Angular最佳实践提出不要在模板中做比较复杂的计算或函数。
这里面的问题很明显,我们每次在输入的时候,或者说在没有敲下enter键之前,这些多余的模板表达式计算或者说dom更新完全没有必要。但是Angular根本不知道什么时候需要计算,所以如何告诉Angular说,嘿,这个没必要检测或者更新呢?
- onPush检测机制
Angular有两个检测机制,那么onPush检测机制就是用来解决上述的情景问题的。如果使用onPush机制那么就是告诉Angular在执行检测的时候,跳过当前组件内部的检测,那么什么时候更新呢?那就是只有当组件的input down输入有新值(变化)或内部有事件event up输出的时候,或者说组件再次通知Angular的检测机制来更新组件。
注意,在Angular的onPush检测机制中,只要组件的输入值input变化,组件内部才会执行更新。但是在Javascript中,有两个基本类型数据,一个是简单数据类型,比如'abc'、123、或者undefined这些都是简单数据类型;还有一个是对象数据类型。简单数据类型在作为参数的时候是按值传递的,而复杂数据类型是按引用传递的。所以,如果按引用传递的值如Array或object数据类型发生变化,组件是不会更新的。
比如:
<button (click)="add()">add</button><button (click)="add()">add</button>
我们在App组件加入如下代码,另外组件代码新增一个方法:
add (): void {
console.log(123)
this.employeeList.push(new Employee('haha', 15))
}
当你点击按钮的时候,方法是执行了,但是数据根本没有变化。所以在这里注意,onPush检测机制的执行条件是只有当传入的值有变化,这里需要明白,简单类型是值变化,而复杂类型是引用变化,才会执行组件内部的state更新然后更新视图。
所以我们的add方法应该更改:
add (): void {
this.employeeList.unshift(new Employee('haha', 15))
this.employeeList = this.employeeList.slice()
}
- 合理的组件拆分可能会事半功倍
老手的开发者,一开始就会发现问题,这里的EmployeeList组件其实是可以一分为二的。那么更改后的组件划分为AddEmployeeList组件、EmployeeList组件,而EmployeeList内部采用onPush检测机制,然后再用内存分析发现,优化的效果会更加明显。他山之石可以攻玉,赶快动手,自己实践一下吧。最后,祝大家新年快乐,工作顺利。