angular变更检测机制,

  angular变更检测机制,

  本文带你了解Angular中的onPush变化检测策略,希望对你有所帮助!

  

默认的变更检测策略

  默认情况下,Angular使用changedetection策略。更改检测的默认策略。

  默认策略没有预先对应用程序做任何假设。因此,每当应用程序中的数据将被用户事件、计时器、XHR、承诺和其他事件改变时,将在所有组件中执行改变检测。

  这意味着从点击事件到从ajax调用接收的数据都将触发变化检测。

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

  @组件({

  模板:` 1

  h1你好{{name}}!/h1

  { { runchangeedetection } }

  `

  })

  导出HelloComponent类{

  @ Input()name:string;

  获取runchangeedetection(){

  console.log(检查视图);

  返回true

  }

  } @组件({

  模板:` 1

  您好/您好

  按钮(单击)=onClick()触发更改检测/按钮

  `

  })

  导出类AppComponent {

  onClick() {}

  }执行完上面的代码,每当我们点击按钮的时候。Angular将执行一个变化检测循环,我们可以在控制台中看到两行“检查视图”日志。

  这种技术被称为脏检查。为了知道视图是否需要更新,Angular需要访问新值,并将其与旧值进行比较,以确定视图是否需要更新。

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

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

  

OnPush变更检测策略

  我们可以将组件的changetectionstrategy设置为changetectionstrategy。OnPush

  这将告诉Angular组件只依赖于它的@inputs(),并且只需要在以下情况下检查它:

  

1. Input引用发生改变

  通过设置onPush变化检测策略,我们同意Angular强制使用不可变对象(或稍后将引入的可观察对象)。

  在变化检测的上下文中使用不可变对象的优点是Angular可以通过检查引用是否发生了变化来确定是否需要检查视图。这样会比深入检查容易很多。

  让我们试着修改一个对象,看看结果。

  @组件({

  选择器:“工具提示”,

  模板:` 1

  h1{{config.position}}/h1

  { { runchangeedetection } }

  `,

  change detection:changeedetectionstrategy。OnPush

  })

  导出类TooltipComponent {

  @ Input()config;

  获取runchangeedetection(){

  console.log(检查视图);

  返回true

  }

  } @组件({

  模板:` 1

  工具提示[config]= config /工具提示

  `

  })

  导出类AppComponent {

  配置={

  位置:“顶部”

  };

  onClick() {

  this . config . position= bottom ;

  }

  }这时,当你点击按钮时,你看不到任何日志。这是因为Angular会比较新旧值的参照,类似于:

  /**在我们的情况下返回false */

  if( oldValue!==新值){

  runchangeedetection();

  }值得一提的是,数字、布尔、字符串、null和undefined都是原语类型。的所有基本类型都是通过值传递的。对象、数组和函数也通过值传递,只是值是引用地址的副本。

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

  @组件({

  模板:` 1

  工具提示[config]= config /工具提示

  `

  })

  导出类AppComponent {

  配置={

  位置:“顶部”

  };

  onClick() {

  this.config={

  位置:“底部”

  }

  }

  }更改对象引用后,我们会看到视图已被检查,并显示新值。

  

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

  当事件在组件或其子组件中被触发时,该组件的内部状态将被更新。

  例如:

  @组件({

  模板:` 1

  按钮(点击)=add()添加/按钮

  {{count}}

  `,

  change detection:changeedetectionstrategy。OnPush

  })

  导出分类子组件{

  count=0;

  add() {

  this.count

  }

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

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

  你会发现这个规则只适用于数字正射影像图事件,下面这些应用程序接口并不会触发变更检测:

  @组件({

  模板:` 1.`,

  更改检测:changeedetectionstrategy .OnPush

  })

  导出分类子组件{

  count=0;

  构造函数(){

  setTimeout(()=this.count=5,0);

  setInterval(()=this . count=5100);

  Promise.resolve().然后(()=这个。计数=5);

  this.http.get(https://count.com ).订阅(res={

  this.count=res

  });

  }

  add() {

  这个.计数

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

  

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

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

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

  @组件({

  选择器:"计数器",

  模板:` {{count}} `,

  更改检测:changeedetectionstrategy .OnPush

  })

  导出分类子组件{

  count=0;

  构造函数(私有cdr:changededetorref){

  setTimeout(()={

  这个。计数=5;

  这个。cdr。检测更改();

  }, 1000);

  }

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

  tick() {

  尝试{

  这个.查看次数。foreach((view)=view。检测更改());

  .

  } catch (e) {

  .

  }

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

  markForCheck(): void {

  markParentViewsForCheck(这个.查看);

  }

  导出函数markParentViewsForCheck(视图:查看数据){

  let currView:view data null=view;

  while (currView) {

  if (currView.def.flags ViewFlags .OnPush) {

  currView.state =ViewState .已启用检查

  }

  货币视图=货币视图。viewcontainerparent curr视图。父母;

  }

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

  

Angular Async pipe

  异步管道会订阅一个可观察量或承诺,并返回它发出的最近一个值。

  让我们看一个输入()是可观察量的onPush组件。

  @组件({

  模板:` 1

  按钮(点击)=add()添加/按钮

  应用列表[项目$]=项目$ /应用列表

  `

  })

  导出类AppComponent {

  items=[];

  items $=新行为主体(这个。项);

  add() {

  这个。项目。推({题目:数学。random()})

  这. items美元.下一个(这个。项);

  }

  } @组件({

  模板:` 1

  div * ngFor= let item of _ items { { item . title } }/div

  `,

  更改检测:changeedetectionstrategy .OnPush

  })

  导出类列表组件实现OnInit {

  @ Input()items:可观察项;

  _ items:Item[];

  ngOnInit() {

  this.items.subscribe(items={

  这个. items=items项

  });

  }

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

  现在,让我们加上异步管道试试。

  @组件({

  模板:` 1

  div * ngFor= let item of items async { item。标题} }/div

  `,

  更改检测:changeedetectionstrategy .OnPush

  })

  导出类列表组件实现OnInit {

  @Input()项;

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

  private _ updateLatestValue(async:any,value: Object): void {

  if (async===this ._obj) {

  这个. latestValue=value

  这个. ref。markforcheck();

  }

  }棱角分明为我们调用markForCheck(),所以我们能看到视图更新了即使投入的引用没有发生改变。

  快速提示:把你的主题暴露在外面是不值得提倡的。始终使用asObservable()方法来公开可观察对象。

  

onPush和视图查询

   @组件({

  选择器:“应用程序-选项卡”,

  模板:` ng-content/ng-content

  })

  导出类选项卡组件实现OnInit {

  @ content child(tab component)tab:tab component;

  ngAfterContentInit() {

  setTimeout(()={

  this . tab . Content= Content ;

  }, 3000);

  }

  } @组件({

  选择器:“应用程序标签”,

  模板:` {{content}} `,

  change detection:changeedetectionstrategy。OnPush

  })

  导出类TabComponent {

  @Input()内容;

  }应用程序标签

  应用程序选项卡/应用程序选项卡

  /app-tabs也许你会认为Angular会在3秒内用新内容更新tab组件。

  毕竟,我们从onPush组件更新输入引用,这将触发变化检测,不是吗?

  但是,在这种情况下,它不会生效。Angular不知道我们在更新tab组件的输入属性。在模板中定义input()是让Angular知道应该在变化检测循环中检查该属性的唯一方法。

  例如:

  应用程序标签

  应用程序标签[内容]=内容/应用程序标签

  /app-tabs因为当我们在模板中显式定义input()时,Angular会创建一个名为updateRenderer()的方法,这个方法会在每个变化检测循环中跟踪内容的值。

  在这种情况下,简单的解决方案是使用setter,然后调用markForCheck()。

  @组件({

  选择器:“应用程序标签”,

  模板:` 1

  {{_content}}

  `,

  change detection:changeedetectionstrategy。OnPush

  })

  导出类TabComponent {

  _内容;

  @Input()设置内容(值){

  这个。_ content=value

  this . cdr . markforcheck();

  }

  构造函数(私有cdr:changededetorref){ }

  }

=== onPush++

  了解了onPush的强大之后,让我们用它来创建一个更高性能的应用。推进组件越多,需要执行的角度检查就越少。让我们来看看你的真实例子:

  我们还有另一个todos组件,它有一个todos作为输入()。

  @组件({

  选择器:“应用程序-待办事项”,

  模板:` 1

  div * ngFor= todos的todo

  { { todo . title } }-{ { runchangedevention } }

  /div

  `,

  change detection:changeedetectionstrategy。OnPush

  })

  将类导出到组件{

  @ Input()todos;

  获取runchangeedetection(){

  console.log(TodosComponent -检查视图);

  返回true

  }

  } @组件({

  模板:` 1

  按钮(点击)=add()添加/按钮

  app-todos[todos]= todos /app-todos

  `

  })

  导出类AppComponent {

  todos=[{ title:一 },{ title:二 }];

  add() {

  this.todos=[.this.todos,{ title:三 }];

  }

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

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

  我们白执行了变更检测!

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

  @组件({

  选择器:“应用程序-待办事项”,

  模板:` 1

  app-todo[todo]= todo * ngFor= let todo of todos /app-todo

  `,

  change detection:changeedetectionstrategy。OnPush

  })

  将类导出到组件{

  @ Input()todos;

  }

  @组件({

  选择器:“应用程序-待办事项”,

  template:` { { todo . title } } { { runchangedevention } } `,

  change detection:changeedetectionstrategy。OnPush

  })

  将类导出到组件{

  @ Input()todo;

  获取runchangeedetection(){

  console.log(TodoComponent -检查视图);

  返回true

  }

  }现在,当我们点击添加按钮时,我们只会在控制台中看到一个日志,因为其他todo组件的输入没有改变,所以我们不会检查它们的视图。

  此外,通过创建更小粒度的组件,我们的代码变得更加可读和可重用。

  有关编程的更多信息,请访问:编程视频!以上是Angular中onPush变化检测策略细节的快速理解。更多请关注我们的其他相关文章!

郑重声明:本文由网友发布,不代表盛行IT的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。

留言与评论(共有 条评论)
   
验证码: