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的观点,版权归原作者所有,仅为传播更多信息之目的,如有侵权请联系,我们将第一时间修改或删除,多谢。