Angular 性能提高之 —— OnPush

变更检测是什么

当我们在 model 中改变数据时,框架层需要知道:

  • model 哪里发生了改变
  • view 中哪里需要更新

整个angular app 是个组件树,不可能任意一个组件中的数据发生变化,所有的组件都更新,性能比较低

Angular 除了默认的变化检测机制,也提供了ChangeDetectionStrategy.OnPush,用 OnPush 可以跳过某个组件或者某个父组件以及它下面所有子组件的变化检测。

默认的变更检测策略

默认情况下,Angular使用ChangeDetectionStrategy.Default策略来进行变更检测。

默认策略并不事先对应用做出任何假设,因此,每当用户事件、记时器、XHRpromise等事件使应用中的数据将发生了改变时,所有的组件中都会执行变更检测。

这种技术被称作脏检查。为了知道视图是否需要更新,Angular需要访问新值并和旧值比较来判断是否需要更新视图。

现在想象一下,如果有一个有成千上万个表达式的大应用,Angular去检查每一个表达式,我们可能会遇到性能上的问题

onPush 变更策略

@Component里添加changeDetection: ChangeDetectionStrategy.OnPush启用onPush变更策略

这将告诉angular该组件仅依赖于它的@inputs(),只有以下情况才需要检查

1.input发生变化

在变更检测的上下文中使用不可变对象的好处是,Angular可以通过检查引用是否发生了改变来判断视图是否需要检查。这将会比深度检查要容易很多。

numbers, booleans, strings, null undefined都是原始类型。所有的原始类型都是按值传递的. Objects, arrays, 还有 functions 也是按值传递的,只不过值是

为了触发对该组件的变更检测,我们需要更改这个object的引用,来触发变更检测

例如:

//test.component.ts
data: any = {
   Test1: 1,
   Test2: 1
};
 data2:any = {
   Test1: 333,
   Test2: 333
}
 onClick() {
   this.data = this.data2
   console.log('Test1',this.data.Test1,'Test2',this.data.Test2);
   
}

2.源于该组件或其子组件的事件

当在一个组件或者其子组件中触发了某一个事件时,这个组件的内部状态会更新。

例如:

//testChild.component.ts
@Component({
 selector: 'app-test-child',
 template: `<div (click)="onClick()">Test2 <span>{{data.Test2}}</span></div>`,
 changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestChildComponent implements OnInit {
 @Input() data: any;
 ngOnInit():void{
}
 onClick() {
   ++this.data.Test2;
}
}

点击这个div时,执行了变更检测,并更新了视图

注意,此时的this.data.Test2会变成父组件的++this.data.Test2

3. 显示的去执行变更检测

angular 提供了三种方法来触发变更检测

第一个是detectChanges()来告诉该组件和它的子组件中去执行变更检测

constructor(private cdr: ChangeDetectorRef) {
   setTimeout(() => {
     this.data.Test2 = 9;
     this.cdr.detectChanges();
  }, 1000);
}

第二个是ApplicationRef.tick(),它告诉Angular来对整个应用程序执行变更检测。

tick() {
 try {
   this._views.forEach((view) => view.detectChanges());
   ...
} catch (e) {
   ...
}
}

第三是markForCheck(),它不会触发变更检测。相反,它会将所有设置了onPush的祖先标记,在当前或者下一次变更检测循环中检测。

markForCheck(): void { 
 markParentViewsForCheck(this._view); 
}

export function markParentViewsForCheck(view: ViewData) {
 let currView: ViewData|null = view;
 while (currView) {
   if (currView.def.flags & ViewFlags.OnPush) {
     currView.state |= ViewState.ChecksEnabled;
  }
   currView = currView.viewContainerParent || currView.parent;
}
}

代码

test.component.ts

import { Component, OnInit } from '@angular/core';
@Component({
 selector: 'app-test',
 template: `
             <div>
               Test1 <span>{{data.Test1}}</span>
             </div>
             <app-test-child [data]="data"></app-test-child>
             <button (click)="onClick()">Click</button>
           `
})
export class TestComponent implements OnInit {
 ngOnInit(): void {
}
 data: any = {
   Test1: 1,
   Test2: 1
};
 data2:any = {
   Test1: 333,
   Test2: 333
}
 onClick() {
   ++this.data.Test1;
   ++this.data.Test2;
   // this.data = this.data2
   console.log('Test1',this.data.Test1,'Test2',this.data.Test2);
   
}
}

testChild.component.ts

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

@Component({
 selector: 'app-test-child',
 template: `<div (click)="onClick()">Test2 <span>{{data.Test2}}</span></div>`,
 changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestChildComponent implements OnInit {
 @Input() data: any;
 // constructor(private cdr: ChangeDetectorRef) {
 //   setTimeout(() => {
 //     this.data.Test2 = 9;
 //     this.cdr.detectChanges();
 //   }, 1000);
 // }
 ngOnInit():void{
}
 onClick() {
   ++this.data.Test2;
}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值