Angular2的雙向數據綁定


什么是雙向綁定

如圖:

 
雙向綁定.jpg

雙向綁定機制維護了頁面(View)與數據(Data)的一致性。如今,MVVM已經是前段流行框架必不可少的一部分。

Angular2中的雙向綁定

雙向綁定,也是Angular2的核心概念之一,Angular2的雙向綁定是這樣的:

  • data=>view:數據綁定,模板語法是 []
  • view=>data:事件綁定,模板語法是 ()
  • Angular其實並沒有一個雙向綁定的實現,他的雙向綁定就是數據綁定+事件綁定,模板語法是 [()] 。

Angular2官方給的例子:

<!--value是數據綁定,input是事件綁定--> <input [value]="currentHero.name" (input)="currentHero.name=$event.target.value" > <!--等價--> <input [(ngModel)]="currentHero.name"> 

上面是input空間的雙向綁定語法,很清楚的說明了雙向綁定與兩個單向綁定的關系。這里沒有使用ngModule語法,ngModule語法內部實現與這個差不多。

事件綁定

  1. 用戶操作出發DOM事件通知
  2. Angular監聽到了通知,然后執行模板語法,上面的例子就是將input控件的輸入值賦給了currentHero.name

數據綁定

由於js語言並沒有屬性變化通知的機制,所以angular也不知道誰發生了變化,在什么時候變了。Angular的變化機制是:

 
image.png

上面的例子中input的數據綁定過程如下:

  1. 代碼修改了currentHero.name的值。
  2. 觸發整個組件樹的變化檢查。
  3. input顯示了修改后的值。
數據何時變化

主要入下集中情況可能改變數據:

  • 用戶輸入操作,比如點擊,提交等。
  • 請求服務端數據。
  • 定時事件,比如setTimeoutsetInterval

這幾點有個共同點,就是他們都是異步的。也就是說,所有的異步操作是可能導致數據變化的根源因素。

如何通知變化

在Angularjs中是由代碼$scope.$apply()或者$scope.$digest觸發,而Angular2接入了ZoneJS,由它監聽了Angular所有的異步事件。ZoneJS重寫了所有的異步API(所謂的猴子補丁,MonkeyPath)。ZoneJS會通知Angular可能有數據發生變化,需要檢測更新。

變化檢測原理 -- 臟檢查

所謂臟檢查就是存儲所有變量的值,每當可能有變量發生變化需要檢查時,就將所有變量的舊值跟新值進行比較,不相等就說明檢測到變化,需要更新對應的視圖。

AngularJS與Angular2變化檢測的區別

Angularjs的變化檢測機制也是臟檢查,而Angular2的變化檢測性能比Angularjs提升了很多。

Angular2

Angular的核心是組件化,組件的嵌套會使得最終形成一棵組件樹。Angular的變化檢測可以分組件進行,每個組件都有對應的變化檢測器ChangeDetector。可想而知,這些變化檢測器也會構成一棵樹。

另外,Angular的數據流是自頂而下的,從父組件到子組件單向流動。單向數據流向保證了高效、可預測的變化檢測,盡管檢查了負組件之后,自組件可能會改變父組件的數據使得父組件需要再次被檢查,這是不被推薦的數據處理方式。在開發模式下,Angular會進行二次檢查,如果出現上述情況,二次檢查就會報錯:ExpressionChangedAfterItHasBeenCheckedError(關於這個問題的答案,可以在參考資料中找到)。而在生產環境中,臟檢查只會執行一次。

Angularjs

相比之下,Angularjs采用的是雙向數據流,錯綜復雜的數據流使得他不得不多次檢查,使得數據最終趨向穩定。理論上,數據永遠不可能穩定,Angularjs的策略是,臟檢查超過10次就認定程序有問題。

 
angular2-change-detection-moscowjs-31-9-638.jpg

變化檢測優化

優化策略

有2個思路:

  1. OnPush策略:我知道我沒變,別查我。
  2. 手動控制刷新:我變了,只查我。

變化檢測策略 OnPush

Angular還讓開發者擁有制定變化策略的能力。

export enum ChangeDetectionStrategy { OnPush, // 表示變化檢測對象的狀態為`CheckOnce` Default, // 表示變化檢測對象的狀態為`CheckAlways` } 

ChangeDetectionStrategy可以看到,Angular有兩種變化檢測策略。Default是Angular默認的變化檢測策略,也就是臟檢查(只要有值發生變化,就全部檢查)。開發者可以根據場景來設置更加高效的變化檢測方式:OnPushOnPush策略,就是只有當輸入數據的引用發生變化或者有事件觸發時,組件進行變化檢測。

@Component({ template: ` <h2>{{vData.name}}</h2> <span>{{vData.email}}</span> `, // 設置該組件的變化檢測策略為onPush changeDetection: ChangeDetectionStrategy.OnPush }) class VCardCmp { @Input() vData; } 

比如上面這個例子,當vData的屬性值發生變化的時候,這個組件不會發生變化檢測,只有當vData重新賦值的時候才會。一般,只接受輸入的木偶子組件(dumb components)比較適合采用onPush策略。

那什么時候只要對象的屬性值發生變化,整個對象的引用就變了呢?不可變對象(Immutable Object)。當組件中的輸入對象是不變量時,可采用onPush變化檢測策略,減少變化檢測的頻率。換個角度來說,為了更加智能地執行變化檢測,可以在只接受輸入的子組件中采用onPush策略。

手動控制變化檢測

Angular不僅可以讓開發者設置變化檢測策略,還可以讓開發者獲取變化檢測對象引用ChangeDetectorRef,手動去操作變化檢測。變化檢測對象引用給開發者提供的方法有以下幾種:

  • markForCheck():將檢查組件的所有父組件所有子組件,即使設置了變化檢測策略為onPush
  • detach():將變化檢測對象脫離檢測對象樹,不再進行變化檢查;結合detectChanges可實現局部變化檢測。(采用onPush策略之后的組件detach()無效)
  • detectChanges():將檢測該組件及其子組件,結合detach可實現局部檢測。
  • checkNoChanges(): 檢測該組件及其子組件,如果有變化存在則報錯,用於開發階段二次驗證變化已經完成。
  • reattach():將脫離的變化檢測對象重新鏈接到變化檢測樹上。

那么,如果是Observable的話,它會訂閱所有的變量變化,只要在訂閱回調函數中手動觸發變化檢測即可實現最小成本的檢測(仍采用onPush變化檢測策略)。舉個例子:

@Component({ template: '{{counter}}', changeDetection: ChangeDetectionStrategy.OnPush }) class CartBadgeCmp { @Input() addItemStream:Observable<any>; counter = 0; constructor(private cd: ChangeDetectorRef) {} ngOnInit() { this.addItemStream.subscribe(() => { this.counter++; // 數據模型發生變化 this.cd.markForCheck(); // 手動觸發檢測 }) } } 

另外,當數據模型變化太過頻繁,我們可自定義變化檢測的時機。舉個例子:

@Component({ template: `{{counter}} <input type="check" (click)="toggle()">`, }) class CartBadgeCmp { counter = 0; detectEnabled = false; constructor(private cd: ChangeDetectorRef) {} ngOnInit() { // 每10毫秒增加1 setInterval(()=>{this.counter++}, 10); } toggle(){ if( this.detectEnabled ){ this.cd.reattach(); // 鏈接上變化檢測樹 } else{ this.cd.detach(); // 脫離變化檢測樹 } } } 

總結

Angular與Angularjs都采用變化檢測機制,前者優於后者主要體現在:

  • 單項數據流動
  • 以組件為單位維度獨立進行檢測
  • 生產環境只進行一次檢查
  • 可自定義的變化檢測策略:DefaultonPush
  • 可自定義的變化檢測操作:markForcheck()detectChanges()detach()reattach()checkNoChanges()
  • 代碼實現上的優化,據說采用了VM friendly的代碼。




鏈接:https://www.jianshu.com/p/cee44e8831c9


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM