ChangeDetectionStrategy.OnPush没有按照我的预期行事

Ser*_*ern 3 typescript angular

我试图让familliar与角2的ChangeDetectionStrategy.OnPush性能提升(如解释在这里).但我这里有古玩的案例.

我有父母AppComponent:

@Component({
  selector: 'my-app',
  template: `<h1>
  <app-literals [title]="title" [dTitle]="dTitle"></app-literals>
  <input [value]="title.name"/>
</h1>
`
})
export class AppComponent implements OnInit {
  title = { name: 'original' };
  dTitle = { name: "original" };

  constructor(private changeDetectorRef : ChangeDetectorRef) {

  }

    ngOnInit(): void {
      setTimeout(() => {
        alert("About to change");
        this.title.name = "changed";
        this.dTitle = { name: "changed" };
      }, 1000);
    }

}
Run Code Online (Sandbox Code Playgroud)

和子LiteralsComponent组件:

@Component({
  selector: 'app-literals',
  template: `  {{title.name}}
  {{dTitle.name}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class LiteralsComponent implements OnInit {
  @Input('title') title;
  @Input('dTitle') dTitle;

  constructor() { }

  ngOnInit() {
  }

}
Run Code Online (Sandbox Code Playgroud)

我认为设置策略以OnPush使角度仅反映参考的变化,但在样本中我尝试改变(变异)对象的属性并且角度仍然反映它.

this.title.name = "changed"; 不应该被检测到(因此UI不应该反映变化).

这是关于plunker的案例

怎么会?怎么做对了?

Max*_*kyi 8

如果我理解正确,你会问为什么绑定值在LiteralsComponent模板中更新,即使你不修改引用title,而是改变对象.

简短的回答是因为你修改了两个:

this.title.name = "changed";
this.dTitle = {name: "changed"};
Run Code Online (Sandbox Code Playgroud)

AppComponent.ngOnInit.如果仅修改,this.title.name = "changed"您将看到该模板未更新.

但是,这是一个非常有趣的问题,需要详细探讨

让我们先从this.title没有开始this.dTitle.
首先要了解的是,当您在模板中指定以下内容时:

{{title.name}}
Run Code Online (Sandbox Code Playgroud)

这是Angular的作用.它尝试title在当前组件实例上查找对象,然后从中获取name属性并将其反映在DOM中.但是使用以下配置:

class AppComponent {
    title = { name: 'original' }

    ngOnInit(): void {
      setTimeout(() => {
        alert("About to change");
       this.title.name = "changed";
    }, 1000);
}
}

class LiteralsComponent {
     @Input() title;
}
Run Code Online (Sandbox Code Playgroud)

两个组件中的title对象相同(指向相同的内存位置).

因此,当Angular运行LiteralsComponent组件的更改检测时,它将访问您在此处更改的同一对象AppComponent:

ngOnInit(): void {
  setTimeout(() => {
    alert("About to change");
    this.title.name = "changed";
  }, 1000);
}
Run Code Online (Sandbox Code Playgroud)

这里有趣的观察是,无论是否有变化都没有被发现OnPush:

class LiteralsComponent {
     @Input() title;

     ngOnChanges(changes) {
         // will be triggered only for the first CD cycle,
         // and won't be triggered when `title` is updated
     }
}
Run Code Online (Sandbox Code Playgroud)

现在,最后要了解的是DOM何时更新.根据这篇文章,它在CD期间针对当前组件进行了更新.这意味着如果未检查当前组件,则不会更新DOM.因此,我们指定onPushLiteralsComponent:

changeDetection: ChangeDetectionStrategy.OnPush,
Run Code Online (Sandbox Code Playgroud)

视图不会更新.

但是,它已在您的问题中更新.为什么?

这就dTitle发挥了作用.使用此属性,您实际上正在修改引用,Angular 会检测绑定更改并为LiteralsComponent组件运行CD .我们在上面已经了解到,当运行CD时,DOM会更新.所以Angular也会更新,{{title.name}}因为它指向同一个对象AppComponent,虽然它没有检测到它被改变了.