在使用Angular 進行開發中,我們常用到Angular 中的綁定-模型到視圖的輸入綁定、視圖到模型的輸出綁定以及視圖與模型的雙向綁定。而這些綁定的數值之所以能在視圖與模型之間保持同步,正是得益於Angular中的變化檢測。
簡單來說,變化偵測就是Angular 用來偵測視圖與模型之間綁定的值是否發生了改變,當偵測到模型中綁定的值改變時,則同步到視圖上,反之,當偵測到視圖上綁定的值發生改變時,則回呼對應的綁定函數。
變更監測的關鍵在於如何最小粒度地監測到綁定的值是否發生了改變,那麼在什麼情況下會導致這些綁定的值發生變化呢?我們可以看一下我們常用的幾個場景:
@Component({ selector: 'demo-component', template: ` <h1>{{name}}</h1> <button (click)="changeName()">change name</button> ` }) export class DemoComponent { name: string = 'Tom'; changeName() { this.name = 'Jerry'; } }
我們在模板中透過插值表達式綁定了 name 屬性。當點擊change name按鈕
時,改變了 name 屬性的值,這時模板視圖顯示內容也改變了。
@Component({ selector: 'demo-component', template: ` <h1>{{name}}</h1> ` }) export class DemoComponent implements OnInit { name: string = 'Tom'; constructor(public http: HttpClient) {} ngOnInit() { // 假设有这个./getNewName请求,返回一个新值'Jerry' this.http.get('./getNewName').subscribe((data: string) => { this.name = data; }); } }
我們在這個組件的ngOnInit 函數裡向伺服器端發送了一個Ajax 請求,當這個請求返回結果時,同樣會改變當前模板視圖上綁定的name 屬性的值。
@Component({ selector: 'demo-component', template: ` <h1>{{name}}</h1> ` }) export class DemoComponent implements OnInit { name: string = 'Tom'; constructor() {} ngOnInit() { // 假设有这个./getNewName请求,返回一个新值'Jerry' setTimeout(() => { this.name = 'Jerry'; }, 1000); } }
我們在這個元件的ngOnInit函數裡透過設定一個定時任務,當定時任務執行時,同樣會改變當前視圖上綁定的name屬性的值。
其實,我們不難發現上述三種情況都有一個共同點,就是這些導致綁定值改變的事件都是非同步發生的。
Angular並不是捕捉物件的變動,它採用的是在適當的時機去檢驗物件的值是否被改動,這個時機就是這些非同步事件的發生。
這個時機是由NgZone 這個服務去掌控的,它獲取到了整個應用的執行上下文,能夠對相關的非同步事件發生、完成或異常等進行捕獲,然後驅動Angular的變化監測機制執行。
透過上面的介紹,我們大致明白了變化檢測是如何被觸發的,那麼Angular 中的變化監測是如何執行的呢?
首先我們需要知道的是,對於每一個元件,都有一個對應的變化監測器;即每一個Component 都對應有一個changeDetector
,我們可以在Component 中透過依賴注入來取得到changeDetector
。
而我們的多個Component 是一個樹狀結構的組織,由於一個Component 對應一個changeDetector
,那麼changeDetector
之間同樣是一個樹狀結構的組織。
最後我們要記住的一點是,每次變化監測都是從 Component 樹根開始的。
子元件:
@Component({ selector: 'demo-child', template: ` <h1>{{title}}</h1> <p>{{paramOne}}</p> <p>{{paramTwo}}</p> ` }) export class DemoChildComponent { title: string = '子组件标题'; @Input() paramOne: any; // 输入属性1 @Input() paramTwo: any; // 输入属性2 }
父元件:
@Component({ selector: 'demo-parent', template: ` <h1>{{title}}</h1> <demo-child [paramOne]='paramOneVal' [paramTwo]='paramTwoVal'></demo-child> <button (click)="changeVal()">change name</button> ` }) export class DemoParentComponent { title: string = '父组件标题'; paramOneVal: any = '传递给paramOne的数据'; paramTwoVal: any = '传递给paramTwo的数据'; changeVal() { this.paramOneVal = '改变之后的传递给paramOne的数据'; } }
上面的程式碼中,DemoParentComponent 透過5354ebd8efe1b41f1d27b438d97f8d03a235b53474b58e7b7de01848f33f3365 標籤嵌入了DemoChildComponent,從樹狀結構上來說,DemoParentComponent 是DemoChildComponent 的根節點,而DemoChildComponent 是DemoParentComponent 的葉子節點。
當我們點擊DemoParentComponent 的button 時,會回調到changeVal 方法,然後會觸發變化監測的執行,變化監測流程如下:
首先變更偵測從DemoParentComponent開始:
偵測title 值是否發生了變化:沒有發生變化
偵測paramOneVal 值是否發生了變化:發生了變化(點擊按鈕呼叫changeVal()方法改變的)
檢測paramTwoVal 值是否發生了變化:沒有變化
然後變化檢測進入到葉子節點DemoChildComponent:
偵測title 值是否發生了改變:沒有發生變化
偵測paramOne 是否發生了變化:發生了改變(由於父元件的屬性paramOneVal發生了改變)
變化監測策略
別急,Angular 的開發團隊已經考慮到了這個問題,上述的偵測機制只是一種預設的偵測機制,Angular 也提供一個OnPush 的偵測機制(設定元資料屬性changeDetection: ChangeDetectionStrategy.OnPush )。
OnPush 与 Default 之间的差别:当检测到与子组件输入绑定的值没有发生改变时,变化检测就不会深入到子组件中去。
上面说到我们可以修改组件元数据属性 changeDetection 来修改组件的变化监测策略(ChangeDetectionStrategy.Default 或 ChangeDetectionStrategy.OnPush),除了这个,我们还可以使用 ChangeDetectorRef 来更加灵活的控制组件的变化监测。
Angular 在整个运行期间都会为每一个组件创建 ChangeDetectorRef 的实例,该实例提供了相关方法来手动管理变化监测。有了这个类,我们自己就可以自定义组件的变化监测策略了,如停止/启用变化监测或者按指定路径变化监测等等。
相关方法如下:
markForCheck():把根组件到该组件之间的这条路径标记起来,通知Angular在下次触发变化监测时必须检查这条路径上的组件。
detach():从变化监测树中分离变化监测器,该组件的变化监测器将不再执行变化监测,除非再次手动执行reattach()方法。
reattach():把分离的变化监测器重新安装上,使得该组件及其子组件都能执行变化监测。
detectChanges():手动触发执行该组件到各个子组件的一次变化监测。
使用方法也很简单,直接在组件中注入即可:
@Component({ selector: 'demo-parent', template: ` <h1>{{title}}</h1> ` }) export class DemoParentComponent implements OnInit { title: string = '组件标题'; constructor(public cdRef: ChangeDetectorRef) {} ngOnInit() { this.cdRef.detach(); // 停止组件的变化监测,看需求使用不同的方法 } }
相关推荐:
以上是Angular開發實務(五):深入解析變化監測的詳細內容。更多資訊請關注PHP中文網其他相關文章!