详解Angular中的变更检测(十一)- OnPush策略示例一

197 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第9天,点击查看活动详情

示例一

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <h1>{{runChangeDetection}}</h1>
  `
})
export class MainComponent {
  get runChangeDetection() {
    console.log('Checking the view');
    return true;
  }
}

问题:组件加载完成后,控制台输出了几次Checking the view

这个例子我们之前用过,但是现在变更检测策略是OnPush,情况不一样了。第一次执行tick() 方法,由于view.detectChanges() ,会输出一次Checking the view,如果是dev模式,会执行checkNoChanges(),但是由于是Onpush策略,会跳过该组件。再次执行tick,由于是OnPush会跳过,所以,总共会仅输出一次Checking the view

示例二

@Component({
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <h1>{{count}}</h1>
  `
})
export class MainComponent {
  count: number = 0;
  
   ngAfterViewInit(): void {
    this.count++;
  }
}

问题
组件加载完成后,控制台会不会报错,页面上的count显示为什么?

其实只要第一个例子明白了,这个就容易明白了。控制台里并不会有错误报出,因为tick 中的checkNoChanges对OnPush并没有什么作用。第一次执行tick的时候,页面会被更新成0,当第二次执行tick的时候,count的值已经是1了,但是由于是OnPush,会被跳过,所以页面上还是显示0。

示例三

@Input输入属性改变,触发变更检测。

@Component({
  selector: 'main',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <h1>{{options.name}}</h1>
  `
})
export class MainComponent {
  @Input() options: any;
  
  
   ngAfterViewInit(): void {
    ...
  }
}
@Component({
  selector: 'app',
  styleUrls: ['./app.component.scss'],
  template: `
      <h1>{{count}}</h1>
      <button (click)="normalClick()">Click</button>
			<main [options]="options"></main>`
})
export class AppComponent  {
  options: any = { name: 'Tom' };
  count: number = 0;

   normalClick(): void {
    this.count++;
    this.options.name  += this.count;
  }
}

上面例子中,在App组件中,点击了Button之后,MainComponent中显示什么? 依然显示Tom,并没有改变,这是因为没有触发Main组件的变更检测。那怎么办呢?

修改一下App组件就好了。

@Component({
  selector: 'app',
  styleUrls: ['./app.component.scss'],
  template: `
      <h1>{{count}}</h1>
      <button (click)="normalClick()">Click</button>
			<main [options]="options"></main>`
})
export class AppComponent  {
  options: any = { name: 'Tom' };
  count: number = 0;

   normalClick(): void {
    this.count++;
    this.options = {
      name: 'Jerry' + this.count
    };
  }
}

因为之前在介绍OnPush策略的时候详细的说过,当组件为OnPush策略,当**@Input**发生引用变化,会自动触发变更检测。

那问题来了,假如App组件中options依然如下,但是我还想让Main组件触发变更检测怎么办?

...
export class AppComponent  {
  options: any = { name: 'Tom' };
  count: number = 0;

   normalClick(): void {
    this.count++;
    this.options ='Jerry' + this.count;
  }
}

办法一

可以采用不可变对象(Immutable),也就是说options对象,我们无法改变,如果改变其属性,那么会重新生成一个新的对象(例如C#中的string类型)。这个可以借助于第三方库来实现。

那如果我不想引入第三方库来实现呢?

方法二

但实际项目中,Main组件可能会这么写。

@Component({
  selector: 'main',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <h1>{{name}}</h1>
  `
})
export class MainComponent {
  @Input() options: any;
  name: string = '';

  constructor(private ref: ChangeDetectorRef){}
  
   ngOnInit(){
    this.name = this.options.name;
   }
}

那要怎么办呢?我们需要改写一下App组件如下。

...
export class AppComponent  {
  options: any = { name: 'Tom' };
  count: number = 0;

   normalClick(): void {
    this.count++;
    this.options = {
      name: 'Jerry' + this.count
    };
  }
}

现在要做的就是更新父组件更新options后,Main组件要把name属性更新一下。

改写Main组件,并且利用ngOnChanges来实现name的更新。

@Component({
  selector: 'main',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <h1>{{name}}</h1>
  `
})
export class MainComponent {
  @Input() options: any;
  name: string = '';

  constructor(private ref: ChangeDetectorRef){}
  
  ngOnInit(){
    this.name = this.options.name;
  }

  ngOnChanges(simple: SimpleChanges): void {
    if (simple['options'].firstChange === false) {
      this.name = simple['options'].currentValue.name;
    }
  }
}

办法三

可以采用ngOnCheck钩子来实现。

App组件中无论下面哪种写法都是可以的。

...
export class AppComponent  {
  options: any = { name: 'Tom' };
  count: number = 0;

   normalClick(): void {
    this.count++;
    // 可以
    this.options = {
      name: 'Jerry' + this.count
    };
    // 可以
    this.options.name = 'Jerry' + this.count;
  }
}
@Component({
  selector: 'main',
  changeDetection: ChangeDetectionStrategy.OnPush,
  template: `
    <h1>{{name}}</h1>
  `
})
export class MainComponent {
  @Input() options: any;
  name: string = '';

  constructor(private ref: ChangeDetectorRef){}
  
   ngOnInit(){
    this.name = this.options.name;
   }

  ngDoCheck(){
    if (this.name !== this.options.name) {
      this.name = this.options.name;
      this.ref.detectChanges();
    }
  }
}

如果对ngDoCheck钩子不太熟悉的小伙伴可以自行查阅资料哦。