千家信息网

Angular中的onPush变更检测策略有哪些

发表于:2026-11-02 作者:千家信息网编辑
千家信息网最后更新 2026年11月02日,这篇文章给大家介绍Angular中的onPush变更检测策略有哪些,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。默认的变更检测策略默认情况下,Angular使用ChangeDet
千家信息网最后更新 2026年11月02日Angular中的onPush变更检测策略有哪些

这篇文章给大家介绍Angular中的onPush变更检测策略有哪些,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

默认的变更检测策略

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

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

这意味着从点击事件到从ajax调用接收到的数据之类的任何事件都会触发更改检测。

通过在组件中定义一个getter并且在模板中使用它,我们可以很容易的看出这一点:

@Component({  template: `    

Hello {{name}}!

{{runChangeDetection}} `})export class HelloComponent { @Input() name: string; get runChangeDetection() { console.log('Checking the view'); return true; }}
@Component({  template: `          `})export class AppComponent  {  onClick() {}}

执行以上代码后,每当我们点击按钮时。Angular将会执行一遍变更检测循环,在console里我们可以看到两行"Checking the view"的日志。

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

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

那么有没有办法让我们主动告诉Angular什么时候去检查我们的组件呢?

OnPush变更检测策略

我们可以将组件的ChangeDetectionStrategy设置成ChangeDetectionStrategy.OnPush。

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

1. Input引用发生改变

通过设置onPush变更检测测策略,我们与Angular约定强制使用不可变对象(或稍后将要介绍的observables)。

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

让我们试试来修改一个对象然后看看结果。

@Component({  selector: 'tooltip',  template: `    

{{config.position}}

{{runChangeDetection}} `, changeDetection: ChangeDetectionStrategy.OnPush})export class TooltipComponent { @Input() config; get runChangeDetection() { console.log('Checking the view'); return true; }}
@Component({  template: `      `})export class AppComponent  {  config = {    position: 'top'  };  onClick() {    this.config.position = 'bottom';  }}

这时候去点击按钮时看不到任何日志了,这是因为Angular将旧值和新值的引用进行比较,类似于:

/** Returns false in our case */if( oldValue !== newValue ) {   runChangeDetection();}

值得一提的是numbers, booleans, strings, null 、undefined都是原始类型。所有的原始类型都是按值传递的. Objects, arrays, 还有 functions 也是按值传递的,只不过值是引用地址的副本。

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

@Component({  template: `      `})export class AppComponent  {  config = {    position: 'top'  };  onClick() {    this.config = {      position: 'bottom'    }  }}

将对象引用改变后,我们将看到视图已被检查,新值被展示出来。

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

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

@Component({  template: `        {{count}}  `,  changeDetection: ChangeDetectionStrategy.OnPush})export class CounterComponent {  count = 0;  add() {    this.count++;  }}

当我们点击按钮时,Angular执行变更检测循环并更新视图。

你可能会想,按照我们开头讲述的那样,每一次异步的API都会触发变更检测,但是并不是这样。

你会发现这个规则只适用于DOM事件,下面这些API并不会触发变更检测:

@Component({  template: `...`,  changeDetection: ChangeDetectionStrategy.OnPush})export class CounterComponent {  count = 0;  constructor() {    setTimeout(() => this.count = 5, 0);    setInterval(() => this.count = 5, 100);    Promise.resolve().then(() => this.count = 5);         this.http.get('https://count.com').subscribe(res => {      this.count = res;    });  }  add() {    this.count++;  }

注意你仍然是更新了该属性的,所以在下一个变更检测流程中,比如去点击按钮,count值将会变成6(5+1)。

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

Angular给我们提供了3种方法来触发变更检测。

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

@Component({  selector: 'counter',  template: `{{count}}`,  changeDetection: ChangeDetectionStrategy.OnPush})export class CounterComponent {   count = 0;  constructor(private cdr: ChangeDetectorRef) {    setTimeout(() => {      this.count = 5;      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;  }}

需要注意的是,手动执行变更检测并不是一种"hack",这是Angular有意的设计并且是非常合理的行为(当然,是在合理的场景下)。

Angular Async pipe

async pipe会订阅一个 Observable 或 Promise,并返回它发出的最近一个值。

让我们看一个input()是observable的onPush组件。

@Component({  template: `          `})export class AppComponent {  items = [];  items$ = new BehaviorSubject(this.items);  add() {    this.items.push({ title: Math.random() })    this.items$.next(this.items);  }}
@Component({  template: `     
{{item.title}}
`, changeDetection: ChangeDetectionStrategy.OnPush})export class ListComponent implements OnInit { @Input() items: Observable; _items: Item[]; ngOnInit() { this.items.subscribe(items => { this._items = items; }); }}

当我们点击按钮并不能看到视图更新。这是因为上述提到的几种情况均未发生,所以Angular在当前变更检测循环并不会检车该组件。

现在,让我们加上async pipe试试。

@Component({  template: `    
{{item.title}}
`, changeDetection: ChangeDetectionStrategy.OnPush})export class ListComponent implements OnInit { @Input() items;}

现在可以看到当我们点击按钮时,视图也更新了。原因是当新的值被发射出来时,async pipe将该组件标记为发生了更改需要检查。我们可以在源码中看到:

private _updateLatestValue(async: any, value: Object): void {  if (async === this._obj) {    this._latestValue = value;    this._ref.markForCheck();  }}

Angular为我们调用markForCheck(),所以我们能看到视图更新了即使input的引用没有发生改变。

如果一个组件仅仅依赖于它的input属性,并且input属性是observable,那么这个组件只有在它的input属性发射一个事件的时候才会发生改变。

Quick tip:对外部暴露你的subject是不值得提倡的,总是使用asObservable()方法来暴露该observable。

onPush和视图查询

@Component({  selector: 'app-tabs',  template: ``})export class TabsComponent implements OnInit {  @ContentChild(TabComponent) tab: TabComponent;  ngAfterContentInit() {    setTimeout(() => {      this.tab.content = 'Content';     }, 3000);  }}
@Component({  selector: 'app-tab',  template: `{{content}}`,  changeDetection: ChangeDetectionStrategy.OnPush})export class TabComponent {  @Input() content;}
  

也许你会以为3秒后Angular将会使用新的内容更新tab组件。

毕竟,我们更新来onPush组件的input引用,这将会触发变更检测不是吗?

然而,在这种情况下,它并不生效。Angular不知道我们正在更新tab组件的input属性,在模板中定义input()是让Angular知道应在变更检测循环中检查此属性的唯一途径。

例如:

  

因为当我们明确的在模板中定义了input(),Angular会创建一个叫updateRenderer()的方法,它会在每个变更检测循环中都对content的值进行追踪。

在这种情况下简单的解决办法使用setter然后调用markForCheck()。

@Component({  selector: 'app-tab',  template: `    {{_content}}  `,  changeDetection: ChangeDetectionStrategy.OnPush})export class TabComponent {  _content;  @Input() set content(value) {    this._content = value;    this.cdr.markForCheck();  }  constructor(private cdr: ChangeDetectorRef) {}}

=== onPush++

在理解了onPush的强大之后,我们来利用它创造一个更高性能的应用。onPush组件越多,Angular需要执行的检查就越少。让我们看看你一个真是的例子:

我们又一个todos组件,它有一个todos作为input()。

@Component({  selector: 'app-todos',  template: `     
{{todo.title}} - {{runChangeDetection}}
`, changeDetection: ChangeDetectionStrategy.OnPush})export class TodosComponent { @Input() todos; get runChangeDetection() { console.log('TodosComponent - Checking the view'); return true; }}
@Component({  template: `          `})export class AppComponent {  todos = [{ title: 'One' }, { title: 'Two' }];  add() {    this.todos = [...this.todos, { title: 'Three' }];  }}

上述方法的缺点是,当我们单击添加按钮时,即使之前的数据没有任何更改,Angular也需要检查每个todo。因此第一次单击后,控制台中将显示三个日志。

在上面的示例中,只有一个表达式需要检查,但是想象一下如果是一个有多个绑定(ngIf,ngClass,表达式等)的真实组件,这将会非常耗性能。

我们白白的执行了变更检测!

更高效的方法是创建一个todo组件并将其变更检测策略定义为onPush。例如:

@Component({  selector: 'app-todos',  template: `      `,  changeDetection: ChangeDetectionStrategy.OnPush})export class TodosComponent {  @Input() todos;}@Component({  selector: 'app-todo',  template: `{{todo.title}} {{runChangeDetection}}`,  changeDetection: ChangeDetectionStrategy.OnPush})export class TodoComponent {  @Input() todo;  get runChangeDetection() {    console.log('TodoComponent - Checking the view');    return true;  }}

现在,当我们单击添加按钮时,控制台中只会看到一个日志,因为其他的todo组件的input均未更改,因此不会去检查其视图。

并且,通过创建更小粒度的组件,我们的代码变得更具可读性和可重用性。

关于Angular中的onPush变更检测策略有哪些就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

检测 组件 检查 更新 视图 事件 按钮 属性 策略 循环 情况 方法 对象 表达式 应用 内容 只有 数据 日志 模板 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 互联网科技海报 互联网科技工作委员会毛军 科技平台互联网 互联网科技保险 厦门中拓互联网络科技有限公司 深圳互联网科技公司 百单互联网科技有限公司 丰台科技园 互联网 华脉科技工业互联网产品 晓花互联网科技有限公司网址 健康食品互联网创新科技园项目 亳州好赞互联网科技有限公司 广州有谷有贝互联网科技有限公司 郑州欣桑互联网科技有限公司 科技和互联网行业的特点 绵阳真晨互联网科技有限公司 陕西彩京云互联网科技有限公司 数字科技和互联网金融哪个好 保山浩联互联网科技有限公司 互联网科技自媒体灵感 互联网科技垄断 互联网 新科技 盛创互联网科技有限公司 金融科技与互联网金融相同和不同 互联网校园科技培训 千方科技属于互联网企业吗 世界互联网领先科技成果获奖 互联网第三次科技革命特征 南通创西澳互联网科技有限公司 北京瑞云互联网科技有限公司
0