随着子组件的增多和嵌套,遇到数据改变,但是组件页面没有渲染的问题开始变多。
例如:
此页面为子组件,打开该页面后,原本应该显示已存在的附件,但是现在却显示空。
正确显示:
文章图片
问题显示:没有渲染
文章图片
此问题的出现往往是因为变更检测器没有检测到组件的数据变更,没有进行变更检测。
到底为什么没有检测到,探究了一段时间也没有探究出发生问题根本原因是什么。
但往往可以过手动变更检测来解决,如下述代码的第9行,调用ChangeDetectorRef的detectChanges函数。今天就来讲一讲关于ChangeDetectorRef的相关知识。
1 constructor(private attachmentService: AttachmentService,
2private ref: ChangeDetectorRef) {
3}4 getAttachmentByIds() {
5 this.attachmentService.getAttachmentByIds(this.attachmentIds)
6.subscribe(attachments => {
7this.attachments = attachments;
8// 进行强制变更检测 防止页面不显示attachments的变更
9this.ref.detectChanges();
10})
11}
变更检测 先来说说angular变更检测的两种策略。
Default
OnPush
Angular的组件可以依赖其他的组件来构建应用程序的页面逻辑,最后形成一棵组件树。每个组件都有自己的变更检测器(change detector)。因此,变更检测器的结构也是一棵同构的树
文章图片
当某个组件的状态发生改变时,Angular会从这棵树的根节点开始遍历,出发所有组件节点的变更检测器,这样Angular就知道那些组件的状态发生了改变,需要更新相应的UI。
【angular ChangeDetectorRef】这个过程看似开销很大,但Angular已经进行了大量优化,实际变更检测的速度很快。 这种策略在我们应用组件过多时会对我们的应用产生性能的影响, 不过在不熟悉相关细节的情况下,Default策略是我们最好的选择。
OnPush策略
OnPush策略我目前没有尝试用过,但可以作为了解。
Angular 还提供了一种 OnPush 策略,我们可以修改组件装饰器的
changeDetection
属性来更改变化检测的策略。 如下述代码的第4行。1 @Component({
2selector: 'app-A',
3// 设置变化检测的策略
4changeDetection: ChangeDetectionStrategy.OnPush,
5template: ...
6 })
7 export class AComponent {
8...
9 }
在
OnPush
策略下,只有这几种情况可以触发当前组件的变更检测:- 组件的输入属性(绑定)的引用被改变
- 组件内部触发了异步事件
- 手动触发变更检测
- 当前组件或子组件之一触发了事件, 如click
例如父组件向子组件使用@Input传入一个对象
@Component({
template: `
`
})
export class AppComponent{
people = {
name: '张三'
};
onClick1() {
this.people.name = '李四';
}
onClick2() {
this.people = { name: '李四'};
}
}
父组件调用onClick1函数并不会触发变更检测,因为这仅仅是改变了对象的属性,并没有改变对象的引用。
而onClick2函数才会触发变更检测。
我们可以通过以下的图观察onPush策略下的行为。
当默认变更检测进行时,变更检测器并没有去更新onPush策略那一边的子树。在我们对组件的变更检测十分了解的情况下,使用这种行为可以减少不必要的变更检测从而提高性能。
文章图片
总结:
为了自动检测变化,Angular 默认使用 ChangeDetectionStrategy.Default 策略,可确保我们的 UI 以可预测和高性能的方式显示,在变更组件不超过50个时,适用于大多数应用程序
对于较大的应用程序,可以考虑使用 ChangeDetectionStrategy.OnPush策略。
ChangeDetectorRef 接下来说说手动变更检测。
手动变更检测使用到了angular给我们提供的
ChangeDetectorRef
类,定义了以下几种公共接口。class ChangeDetectorRef {
markForCheck() : void
detach() : void
reattach() : voiddetectChanges() : void
checkNoChanges() : void
}
假设我们有如下组件树
文章图片
detach()
允许我们操作状态的第一个方法是
detach
,它只是单纯禁用对当前视图的检测。使用方法也很简单。
export class AComponent {
constructor(public cd: ChangeDetectorRef) {
this.cd.detach();
}
这确保了在运行以下更改检测时,AppComponent将跳过以 开头的左分支(不会检查背景为黄色的组件)。
同时假如AComponent的状态发生了改变,它的子组件也不会进行检查。
文章图片
reattach
将先前分离的视图重新附加到更改检测树。
例如我们使用
reattach()
方法,就可以将上面使用detach()禁用的视图重新添加进来。例如:
@Input()
set live(value: boolean) {
if (value) {
this.ref.reattach();
} else {
this.ref.detach();
}
}
文章图片
markForCheck
该方法适用于使用
OnPush
策略的时候。当视图使用OnPush (checkOnce) 更改检测策略时,显式将视图标记为脏,以便再次对其进行检查。
从搜索到的资料来看,它只是向上迭代并启用对每个父组件直至根的检查。
文章图片
detectChanges
对当前组件及其所有子组件运行一次更改检测, 也是我们最常用的。
文章图片
checkNoChanges
可确保在当前的变更检测运行中不会发生任何更改。如果发现更改的绑定或确定应该更新 DOM,则抛出异常。
组合操作
比如组件的数据预计会不断变化,每秒多次。为了提高性能,我们希望检查和更新列表的频率低于实际发生更改的频率。为此,我们可以分离组件的更改检测器并每五秒执行一次检查。
@Component({
selector: 'giant-list',
template: `
Data {{d}}
`,
})
class GiantList {
constructor(private ref: ChangeDetectorRef, public dataProvider: DataListProvider) {
ref.detach();
setInterval(() => {
this.ref.detectChanges();
}, 5000);
}
}
总结: 理解ChangeDetectorRef类,可以很好地帮助我们对变更检测的原理和行为,当默认变更检测满足不了我们的想法时,可以让我们手动地去调整视图的更新。
推荐阅读
- Angular的nz-zorro升级
- Angular项目实战Angular服务器渲染常遇的坑,这份填坑指南请收好~
- Angular项目实战Angular5项目模块划分
- #私藏项目实操分享# 如何解决 Angular custom library module 在 ng build 时无法被识别的错误
- #私藏项目实操分享# 使用 RxJs Observable 来避免 Angular 应用中的 Promise 使用
- 如何使用 Visual Studio Code 调试 Angular Schematics 实现
- 刷新/重新加载Angular 2 Web App
- 获取请求在Angular 5 app中抛出404错误
- 需要修复在Azure App服务上运行的Angular 2的路由问题