Angular開發問題記錄:元件資料不能實時更新到檢視上

2022-12-08 22:00:55
工作中碰到一個問題:元件資料不能實時更新到檢視上,問題本身比較容易解決,但還是總結記錄一下。

前端(vue)入門到精通課程,老師線上輔導:聯絡老師
Apipost = Postman + Swagger + Mock + Jmeter 超好用的API偵錯工具:

先來說一下起因吧。【相關教學推薦:《》】

問題起源


MainComponent:

@Component({
  selector: 'main',
  template: `
    <MenuComponent [isReport]="isReport">
	  </MenuComponent>
 `,
  changeDetection:ChangeDetectionStrategy.OnPush
})
export class MainComponent {
  ...
}
登入後複製

現在有一個MainComponent,我需要在這個元件中參照一個另一個元件MenuComponent

import { Component, Input} from '@angular/core';
import { Subject, debounceTime } from 'rxjs';

@Component({
  selector: 'movie',
  styles: ['div {border: 1px solid black}'],
  template: `
    <div (mouseover)="mouseOver()">
      <h3>{{ menu }}</h3>
    </div>`
})
export class MovieComponent {
  @Input() isReport: boolean = false;
  menu: string = '我是Menu';

  mouseOver$: Subject<any> = new Subject();

  ngOnInit(): void {
    this.mouseOver$.pipe(debounceTime(250)).subscribe((data) => {
       this.menu = 'New: ' + this.menu;
    });
  }

  mouseOver(): void {
    this.mouseOver$.next(this.menu);
  }
}
登入後複製

這個MenuComponent在其他的頁面使用起來是正常的,而且因為是Menu元件,所以上面很有多mouseover事件,這些事件也可以正常工作。但,這個 MenuComponent 放在MainComponent中,mouseover事件就有問題了,偵錯了下mouseover事件,程式碼都正確執行了,感覺程式碼並沒有什麼問題。因為這個元件放在其他頁面裡,行為完全正常,所以感覺不是元件本身的問題。

表現的現象是

Menu裡的mouseover行為很怪異,你over到A的時候,顯示的是B的資料,當你over到B的時候顯示的是A的資料,整個錯亂了。

第一反應就是,這會不會是和MainComponent中的mouseover事件衝突了呢?

檢查了一遍,沒有發現問題所在。但是有意外收穫,啊啊啊,MainComponent元件使用的是OnPush變更檢測策略,難怪其他頁面都好使,就這個地方有問題了。好了,問題應該就是OnPush造成的。關於變更檢測策略的,那還不是手到擒來,在熟悉不過了,來來來,一起簡單看一下這個OnPush。

OnPush策略

Angular有兩種變更檢測的策略,一種是Default,另一種就是這個OnPush。OnPush這個變更檢測策略主要為了改善效能。當我們設定元件裝飾器的 changeDetection為OnPush的時候,Angular 每次觸發變更檢測後會跳過該元件和該元件的所以子元件變化檢測

好了,我們也知道什麼是OnPush變更檢測策略了,它會跳過當前元件和其子元件的變更 檢測。也就是說,你改變這個元件的屬性值,但這些屬性值並不會更新到檢視上,也就是元件陣列和檢視不一致。那我們知道了這一點,再回去看一下MenuComponent

由於MainComponent的變更策略設定為了OnPush,他的子元件的變更檢測策略會跳過,也就是MenuComponent變更檢測不起作用了。但是,你會發現當你操作Menu的時候檢視還是會有變化的。這是怎麼回事?

大部分人可能花一分鐘瞭解了OnPush是什麼,但是沒有了解透徹。繼續往下看。

OnPush 策略下,以下這種情況會觸發元件的變化檢測:

當前元件或子元件之一觸發了事件

如果OnPush元件或其子元件之一觸發(DOM/BOM)事件,例如 clickmouseovermouseleaveresize, keydown,則將觸發變化檢測(針對元件樹中的所有元件)。

需要注意的是在OnPush策略中,以下操作不會觸發變化檢測:

  • setTimeout()

  • setInterval()

  • Promise.resolve().then()

  • this.http.get('...').subscribe()

原來如此,儘管是OnPush策略,但是DOM/BOM事件還是會觸發變更檢測的,所以MenuComponent的檢視還是會有變化的,也就是這個變更檢測是起作用的。但問題還是沒有解決,Menu mouseover的時候還是會錯亂啊!再來看一下程式碼。

ngOnInit(): void {
    this.mouseOver$.pipe(debounceTime(250)).subscribe((data) => {
       this.menu = 'New: ' + this.menu;
    });
}
登入後複製

引起問題的地方就是這debounceTime,這個之前在介紹Rxjs原理的時候,說過這個是非同步的。之前掌握的東西,終於派上用場了。

總結一下,就是mouseover是非同步的,會觸發變更檢測,但是由於debounceTime是非同步又巢狀了一下,debounceTime一般是用setTimeout來實現的。所以,debounceTime裡的資料變化並不能及時的顯示到檢視中。終於找到問題的根源了。啦啦啦。問題找到了,那解決起來多easy啊。它不是不會觸發變更檢測嗎,我就手動讓它觸發一下吧。

import { Component, Input, ChangeDetectorRef } from '@angular/core';
import { Subject, debounceTime } from 'rxjs';

@Component({
  selector: 'movie',
  styles: ['div {border: 1px solid black}'],
  template: `...`
})
export class MovieComponent {
  ...

  constructor(private cd: ChangeDetectorRef){}

  ngOnInit(): void {
    this.mouseOver$.pipe(debounceTime(250)).subscribe((data) => {
       this.menu = 'New: ' + this.menu;

       this.cd.detectChanges();
    });
  }

  ...
}
登入後複製

總結


  • 平時多注意知識積累,不能按照網上說的解決方案複製過來就解決了,遇到簡單問題這樣是沒有問題的,遇到複雜的就沒辦法了;

  • 當設定為Onpush策略時,要更加註意,用OnPush就是要減少變更檢測的次數,就不要無論什麼情況都detectChanges,或markForCheck,失去了意義,還是要規範使用;

  • 要優雅實現程式碼,專案中居然還看到把父元件的ChangeDetectorRef作為輸入屬性傳到子元件中,一看就不懂變更檢測啊;

更多程式設計相關知識,請存取:!!

以上就是Angular開發問題記錄:元件資料不能實時更新到檢視上的詳細內容,更多請關注TW511.COM其它相關文章!