角度2变化检测和ChangeDetectionStrategy.OnPush

Ced*_*Ced 28 zonejs angular

我正试图理解这个ChangeDetectionStrategy.OnPush机制.

我从读数中得到的结论是,通过比较旧值和新值来进行变化检测.如果对象引用未更改,则该比较将返回false.

然而,似乎某些情况下绕过了"规则".你能解释一下这一切是如何运作的吗?

Ced*_*Ced 88

好吧,因为这让我花了整整一个晚上才知道我写了一份简历来解决我头脑中的一切,这可能会对未来的读者有所帮助.所以让我们从清除一些事情开始:

变化来自事件

组件可能包含字段.这些字段仅在某种事件发生后才会更改,并且仅在此之后发生.

我们可以将事件定义为鼠标单击,ajax请求,setTimeout ...

数据从上到下流动

角度数据流是单向的.这意味着数据不会从孩子流向父母.仅从父母到孩子,例如通过@Input标签.让上层组件意识到孩子的某些变化的唯一方法是通过一个事件.这让我们:

事件触发器更改检测

当事件发生时,角度框架从上到下检查每个组件以查看它们是否已更改.如果有任何更改,它会相应地更新视图.

事件被触发后,Angular会检查每个组件.假设您在组件上有一个click事件,该组件是最低级别的组件,这意味着它具有父级但没有子级.该点击可以通过事件发射器,服务等触发父组件的更改.Angular不知道父母是否会改变.这就是Angular在默认情况下触发事件后检查每个组件的原因.

要查看他们是否已更改角度,请使用该ChangeDetector课程.

更改检测器

每个组件都附有一个变化检测器类.它用于检查某个组件在某个事件后是否已更改状态,并查看是否应更新该视图.当事件发生时(鼠标单击等),此更改检测过程将针对所有组件进行 - 默认情况下 - .

例如,如果我们有一个ParentComponent:

@Component({
  selector: 'comp-parent',
  template:'<comp-child [name]="name"></comp-child>'
})
class ParentComponent{
  name:string;
} 
Run Code Online (Sandbox Code Playgroud)

我们将附加一个变化检测器ParentComponent,如下所示:

class ParentComponentChangeDetector{
    oldName:string; // saves the old state of the component.

    isChanged(newName){
      if(this.oldName !== newName)
          return true;
      else
          return false;
    }
}
Run Code Online (Sandbox Code Playgroud)

更改对象属性

您可能已经注意到,如果更改对象属性,则isChanged方法将返回false.确实

let prop = {name:"cat"};
let oldProp = prop;
//change prop
prop.name = "dog";
oldProp === prop; //true
Run Code Online (Sandbox Code Playgroud)

因为当对象属性可以改变而不返回true时changeDetector isChanged(),angular将假定每个下面的组件也可能已经改变.因此,它只会检查所有组件中的变化检测.

示例:这里我们有一个带有子组件的组件.虽然更改检测将为父组件返回false,但应该更新子视图.

@Component({
  selector: 'parent-comp',
  template: `
    <div class="orange" (click)="person.name='frank'">
      <sub-comp [person]="person"></sub-comp>
    </div>
  `
})
export class ParentComponent {
  person:Person = { name: "thierry" };     
}

// sub component
@Component({
  selector: 'sub-comp',
  template: `
    <div>
      {{person.name}}
    </div>
})
export class SubComponent{
  @Input("person") 
  person:Person;
}
Run Code Online (Sandbox Code Playgroud)

这就是为什么默认行为是检查所有组件.因为即使子组件的输入没有改变也不能改变,因此angular不确定它的输入是否真的没有改变.传递给它的对象可能是相同的,但它可能具有不同的属性.

OnPush策略

当组件被标记时changeDetection: ChangeDetectionStrategy.OnPush,如果对象引用没有改变,angular将假定输入对象没有改变.意味着更改属性不会触发更改检测.因此,视图将与模型不同步.

这个例子很酷,因为它显示了这一点.您有一个父组件,单击该组件时,输入对象名称属性将更改.如果检查click()父组件内的方法,您会注意到它在控制台中输出子组件属性.那个属性已经改变了.但你看不到它.那是因为视图尚未更新.由于OnPush策略,更改检测过程没有发生,因为ref对象没有改变.

Plnkr

@Component({
  selector: 'my-app',
  template: `
    <div class="orange" (click)="click()">
      <sub-comp [person]="person" #sub></sub-comp>
    </div>
  `
})
export class App {
  person:Person = { name: "thierry" };
  @ViewChild("sub") sub;

  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }
}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div>
      {{person.name}}
    </div>
  `
})
export class SubComponent{
  @Input("person") 
  person:Person;
}

export interface Person{
  name:string,
}
Run Code Online (Sandbox Code Playgroud)

点击后,视图中的名称仍然很大,但不在组件本身中


在组件内触发的事件将触发更改检测.

在这里,我们在原始问题中遇到了困惑.下面的组件标有OnPush策略,但视图在更改时会更新.

Plnkr

@Component({
  selector: 'my-app',
  template: `
    <div class="orange" >
      <sub-comp ></sub-comp>
    </div>
  `,
  styles:[`
    .orange{ background:orange; width:250px; height:250px;}
  `]
})
export class App {
  person:Person = { name: "thierry" };      
  click(){
    this.person.name = "Jean";
    console.log(this.sub.person);
  }

}

// sub component
@Component({
  selector: 'sub-comp',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <div class="grey" (click)="click()">
      {{person.name}}
    </div>
  `,
  styles:[`
    .grey{ background:#ccc; width:100px; height:100px;}
  `]
})
export class SubComponent{
  @Input()
  person:Person = { name:"jhon" };
  click(){
    this.person.name = "mich";
  }
}
Run Code Online (Sandbox Code Playgroud)

所以在这里我们看到对象输入没有改变引用,我们正在使用策略OnPush.这可能会让我们相信它不会更新.实际上它已更新.

正如Gunter在他的回答中所说,这是因为,使用OnPush策略,如果出现以下情况,则会对组件进行更改检测:

  • 在组件本身上接收(单击)绑定事件.
  • @Input()已更新(如ref obj更改)
  • | 异步管道收到一个事件
  • "手动"调用更改检测

无论战略如何.

链接

  • 好的答案,考虑将[本文](https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f)添加到列表中.它解释了引擎盖下变化检测的机制 (2认同)
  • 这是一个极好的答案. (2认同)

Gün*_*uer 21

*ngFor它是否有自己的变化检测.每次运行更改检测时,NgFor都会ngDoCheck()调用其方法,并NgFor检查数组的内容是否已更改.

在你的情况下没有变化,因为构造函数在Angular开始渲染视图之前执行.
如果您要添加一个按钮,例如

<button (click)="persons.push({name: 'dynamically added', id: persons.length})">add</button>
Run Code Online (Sandbox Code Playgroud)

然后点击实际上会导致ngFor必须识别的更改.

随着ChangeDetectionStrategy.OnPush组件中的更改检测将运行,因为OnPush更改检测在运行时运行

  • 收到绑定事件 (click)
  • a @Input()由更改检测更新
  • | async 管接到了一个事件
  • "手动"调用更改检测


yur*_*zui 7

为了防止Application.tick尝试分离changeDetector:

constructor(private cd: ChangeDetectorRef) {

ngAfterViewInit() {
  this.cd.detach();
}
Run Code Online (Sandbox Code Playgroud)

Plunker