中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Angular中的onPush變更檢測策略有哪些

發布時間:2021-09-10 09:43:14 來源:億速云 閱讀:147 作者:柒染 欄目:web開發

這篇文章給大家介紹Angular中的onPush變更檢測策略有哪些,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。

Angular中的onPush變更檢測策略有哪些

默認的變更檢測策略

默認情況下,Angular使用ChangeDetectionStrategy.Default策略來進行變更檢測。

默認策略并不事先對應用做出任何假設,因此,每當用戶事件、記時器、XHR、promise等事件使應用中的數據將發生了改變時,所有的組件中都會執行變更檢測。

這意味著從點擊事件到從ajax調用接收到的數據之類的任何事件都會觸發更改檢測。

通過在組件中定義一個getter并且在模板中使用它,我們可以很容易的看出這一點:

@Component({
  template: `
    <h2>Hello {{name}}!</h2>
    {{runChangeDetection}}
  `
})
export class HelloComponent {
  @Input() name: string;

  get runChangeDetection() {
    console.log('Checking the view');
    return true;
  }
}
@Component({
  template: `
    <hello></hello>
    <button (click)="onClick()">Trigger change detection</button>
  `
})
export class AppComponent  {
  onClick() {}
}

執行以上代碼后,每當我們點擊按鈕時。Angular將會執行一遍變更檢測循環,在console里我們可以看到兩行“Checking the view”的日志。

這種技術被稱作臟檢查。為了知道視圖是否需要更新,Angular需要訪問新值并和舊值比較來判斷是否需要更新視圖。

現在想象一下,如果有一個有成千上萬個表達式的大應用,Angular去檢查每一個表達式,我們可能會遇到性能上的問題。

那么有沒有辦法讓我們主動告訴Angular什么時候去檢查我們的組件呢?

OnPush變更檢測策略

我們可以將組件的ChangeDetectionStrategy設置成ChangeDetectionStrategy.OnPush

這將告訴Angular該組件僅僅依賴于它的@inputs(),只有在以下幾種情況才需要檢查:

1. Input引用發生改變

通過設置onPush變更檢測測策略,我們與Angular約定強制使用不可變對象(或稍后將要介紹的observables)。

在變更檢測的上下文中使用不可變對象的好處是,Angular可以通過檢查引用是否發生了改變來判斷視圖是否需要檢查。這將會比深度檢查要容易很多。

讓我們試試來修改一個對象然后看看結果。

@Component({
  selector: 'tooltip',
  template: `
    <h2>{{config.position}}</h2>
    {{runChangeDetection}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TooltipComponent  {

  @Input() config;

  get runChangeDetection() {
    console.log('Checking the view');
    return true;
  }
}
@Component({
  template: `
    <tooltip [config]="config"></tooltip>
  `
})
export class AppComponent  {
  config = {
    position: 'top'
  };

  onClick() {
    this.config.position = 'bottom';
  }
}

這時候去點擊按鈕時看不到任何日志了,這是因為Angular將舊值和新值的引用進行比較,類似于:

/** Returns false in our case */
if( oldValue !== newValue ) { 
  runChangeDetection();
}

值得一提的是numbers, booleans, strings, null 、undefined都是原始類型。所有的原始類型都是按值傳遞的. Objects, arrays, 還有 functions 也是按值傳遞的,只不過值是引用地址的副本。

所以為了觸發對該組件的變更檢測,我們需要更改這個object的引用。

@Component({
  template: `
    <tooltip [config]="config"></tooltip>
  `
})
export class AppComponent  {
  config = {
    position: 'top'
  };

  onClick() {
    this.config = {
      position: 'bottom'
    }
  }
}

將對象引用改變后,我們將看到視圖已被檢查,新值被展示出來。

2.源于該組件或其子組件的事件

當在一個組件或者其子組件中觸發了某一個事件時,這個組件的內部狀態會更新。 例如:

@Component({
  template: `
    <button (click)="add()">Add</button>
    {{count}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
  count = 0;

  add() {
    this.count++;
  }

}

當我們點擊按鈕時,Angular執行變更檢測循環并更新視圖。

你可能會想,按照我們開頭講述的那樣,每一次異步的API都會觸發變更檢測,但是并不是這樣。

你會發現這個規則只適用于DOM事件,下面這些API并不會觸發變更檢測:

@Component({
  template: `...`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent {
  count = 0;

  constructor() {
    setTimeout(() => this.count = 5, 0);

    setInterval(() => this.count = 5, 100);

    Promise.resolve().then(() => this.count = 5); 
    
    this.http.get('https://count.com').subscribe(res => {
      this.count = res;
    });
  }

  add() {
    this.count++;
  }

注意你仍然是更新了該屬性的,所以在下一個變更檢測流程中,比如去點擊按鈕,count值將會變成6(5+1)。

3. 顯示的去執行變更檢測

Angular給我們提供了3種方法來觸發變更檢測。

第一個是detectChanges()來告訴Angular在該組件和它的子組件中去執行變更檢測。

@Component({
  selector: 'counter',
  template: `{{count}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class CounterComponent { 
  count = 0;

  constructor(private cdr: ChangeDetectorRef) {

    setTimeout(() => {
      this.count = 5;
      this.cdr.detectChanges();
    }, 1000);

  }

}

第二個是ApplicationRef.tick(),它告訴Angular來對整個應用程序執行變更檢測。

tick() {
 
  try {
    this._views.forEach((view) => view.detectChanges());
    ...
  } catch (e) {
    ...
  }
}

第三是markForCheck(),它不會觸發變更檢測。相反,它會將所有設置了onPush的祖先標記,在當前或者下一次變更檢測循環中檢測。

markForCheck(): void { 
  markParentViewsForCheck(this._view); 
}

export function markParentViewsForCheck(view: ViewData) {
  let currView: ViewData|null = view;
  while (currView) {
    if (currView.def.flags & ViewFlags.OnPush) {
      currView.state |= ViewState.ChecksEnabled;
    }
    currView = currView.viewContainerParent || currView.parent;
  }
}

需要注意的是,手動執行變更檢測并不是一種“hack”,這是Angular有意的設計并且是非常合理的行為(當然,是在合理的場景下)。

Angular Async  pipe

async pipe會訂閱一個 Observable 或 Promise,并返回它發出的最近一個值。

讓我們看一個input()是observable的onPush組件。

@Component({
  template: `
    <button (click)="add()">Add</button>
    <app-list [items$]="items$"></app-list>
  `
})
export class AppComponent {
  items = [];
  items$ = new BehaviorSubject(this.items);

  add() {
    this.items.push({ title: Math.random() })
    this.items$.next(this.items);
  }
}
@Component({
  template: `
     <div *ngFor="let item of _items ; ">{{item.title}}</div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListComponent implements OnInit {
  @Input() items: Observable<Item>;
  _items: Item[];
  
  ngOnInit() {
    this.items.subscribe(items => {
      this._items = items;
    });
  }

}

當我們點擊按鈕并不能看到視圖更新。這是因為上述提到的幾種情況均未發生,所以Angular在當前變更檢測循環并不會檢車該組件。

現在,讓我們加上async pipe試試。

@Component({
  template: `
    <div *ngFor="let item of items | async">{{item.title}}</div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class ListComponent implements OnInit {
  @Input() items;
}

現在可以看到當我們點擊按鈕時,視圖也更新了。原因是當新的值被發射出來時,async pipe將該組件標記為發生了更改需要檢查。我們可以在源碼中看到:

private _updateLatestValue(async: any, value: Object): void {
  if (async === this._obj) {
    this._latestValue = value;
    this._ref.markForCheck();
  }
}

Angular為我們調用markForCheck(),所以我們能看到視圖更新了即使input的引用沒有發生改變。

如果一個組件僅僅依賴于它的input屬性,并且input屬性是observable,那么這個組件只有在它的input屬性發射一個事件的時候才會發生改變。

Quick tip:對外部暴露你的subject是不值得提倡的,總是使用asObservable()方法來暴露該observable。

onPush和視圖查詢

@Component({
  selector: 'app-tabs',
  template: `<ng-content></ng-content>`
})
export class TabsComponent implements OnInit {
  @ContentChild(TabComponent) tab: TabComponent;

  ngAfterContentInit() {
    setTimeout(() => {
      this.tab.content = 'Content'; 
    }, 3000);
  }
}
@Component({
  selector: 'app-tab',
  template: `{{content}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TabComponent {
  @Input() content;
}
<app-tabs>
  <app-tab></app-tab>
</app-tabs>

也許你會以為3秒后Angular將會使用新的內容更新tab組件。

畢竟,我們更新來onPush組件的input引用,這將會觸發變更檢測不是嗎?

然而,在這種情況下,它并不生效。Angular不知道我們正在更新tab組件的input屬性,在模板中定義input()是讓Angular知道應在變更檢測循環中檢查此屬性的唯一途徑。

例如:

<app-tabs>
  <app-tab [content]="content"></app-tab>
</app-tabs>

因為當我們明確的在模板中定義了input(),Angular會創建一個叫updateRenderer()的方法,它會在每個變更檢測循環中都對content的值進行追蹤。

Angular中的onPush變更檢測策略有哪些

在這種情況下簡單的解決辦法使用setter然后調用markForCheck()

@Component({
  selector: 'app-tab',
  template: `
    {{_content}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TabComponent {
  _content;

  @Input() set content(value) {
    this._content = value;
    this.cdr.markForCheck();
  }

  constructor(private cdr: ChangeDetectorRef) {}

}

=== onPush++

在理解了onPush的強大之后,我們來利用它創造一個更高性能的應用。onPush組件越多,Angular需要執行的檢查就越少。讓我們看看你一個真是的例子:

我們又一個todos組件,它有一個todos作為input()。

@Component({
  selector: 'app-todos',
  template: `
     <div *ngFor="let todo of todos">
       {{todo.title}} - {{runChangeDetection}}
     </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodosComponent {
  @Input() todos;

  get runChangeDetection() {
    console.log('TodosComponent - Checking the view');
    return true;
  }

}
@Component({
  template: `
    <button (click)="add()">Add</button>
    <app-todos [todos]="todos"></app-todos>
  `
})
export class AppComponent {
  todos = [{ title: 'One' }, { title: 'Two' }];

  add() {
    this.todos = [...this.todos, { title: 'Three' }];
  }
}

上述方法的缺點是,當我們單擊添加按鈕時,即使之前的數據沒有任何更改,Angular也需要檢查每個todo。因此第一次單擊后,控制臺中將顯示三個日志。

在上面的示例中,只有一個表達式需要檢查,但是想象一下如果是一個有多個綁定(ngIf,ngClass,表達式等)的真實組件,這將會非常耗性能。

我們白白的執行了變更檢測!

更高效的方法是創建一個todo組件并將其變更檢測策略定義為onPush。例如:

@Component({
  selector: 'app-todos',
  template: `
    <app-todo [todo]="todo" *ngFor="let todo of todos"></app-todo>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodosComponent {
  @Input() todos;
}

@Component({
  selector: 'app-todo',
  template: `{{todo.title}} {{runChangeDetection}}`,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoComponent {
  @Input() todo;

  get runChangeDetection() {
    console.log('TodoComponent - Checking the view');
    return true;
  }

}

現在,當我們單擊添加按鈕時,控制臺中只會看到一個日志,因為其他的todo組件的input均未更改,因此不會去檢查其視圖。

并且,通過創建更小粒度的組件,我們的代碼變得更具可讀性和可重用性。

關于Angular中的onPush變更檢測策略有哪些就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

镇平县| 德江县| 富顺县| 德昌县| 于田县| 南召县| 仁布县| 五莲县| 永仁县| 仪陇县| 板桥市| 双城市| 当阳市| 内江市| 油尖旺区| 巨野县| 册亨县| 阿克陶县| 中阳县| 青阳县| 漳平市| 分宜县| 南安市| 长岛县| 柳林县| 乌恰县| 沂南县| 陈巴尔虎旗| 都昌县| 特克斯县| 宣恩县| 乌兰察布市| 徐水县| 望城县| 通州市| 类乌齐县| 沭阳县| 林芝县| 观塘区| 丹阳市| 金溪县|