Angular Pipe 在 嵌套對象上的非預期行爲分析

Angular Pipe 在 嵌套對象上的非預期行爲分析

場景

在工做中,存在一個嵌套對象,須要展現嵌套對象內層的一些信息,因而寫了個Pipe 來處理,可是發現當嵌套的對象發生變化時,pipe 不會從新執行。例若有下面一個數據。javascript

var feer = {
    name: 'joe',
    skills: [
        {
            name:'js'
        },
        {
            name: 'ts'
        }
    ]
}

咱們想要的結果是把skills 裏面的name 所有展現出來,以, 分割。css

// component
export class AppComponent {
  private skills = ['css', 'html', 'java', 'gulp']
  name = 'Angular 6';
  feer = {
    name: 'joe',
    skills: [
      {
        name: 'js'
      },
      {
        name: 'ts'
      }
    ]
  }

  add() {
    const skill = this.skills.shift();
    if (skill) {
      this.feer.skills.push({
        name: skill
      })
    }
  }
}
// html
{{ feer.skills | defaultPure }}
// Pipe
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
    name: 'defaultPure'
})

export class DefaultPurePipe implements PipeTransform {
    transform(feer:any): string {
        return feer.skills.map((v)=>v.name).join(',');
    }
}

在這種狀況下,若是調用add 引發 skills 發生變化,pipe 不會從新計算,顯示的仍是初始值 js,tshtml

問題

這裏存在一個問題,對於上面👆例子中存在的嵌套結構,在 skills 變化的時候 ,transform 並無從新執行。這裏推測多是 內部檢測時候並不會作 deep-change-detection (也就是不會關注 skills 裏的變化),最終致使了上面的問題: skills 發生了變化,但 pipe 並無從新執行計算變動輸出結果。vue

下面是摘自官網的一段話:java

Angular executes a pure pipe only when it detects a pure change to the input value. A pure change is either a change to a primitive input value ( String, Number, Boolean, Symbol) or a changed object reference ( Date, Array, Function, Object).

Angular ignores changes within (composite) objects. It won't call a pure pipe if you change an input month, add to an input array, or update an input object property.git

pipe 忽略了 對象內部複合對象的變更(如例子中的 skills ), angular 不會作深度檢查,當咱們調用 add 方法時候,往 skills 數組裏 push 裏一個對象,對於 angular 來講, skills 是「未」發生變化的,由於引用是同樣的。github

解決方案

@Pipe 的 decorator 中除了name 還有一個叫 pure 類型爲 booleanmetadata,官方解釋以下typescript

If Pipe is pure (its output depends only on its input.) Normally pipe's transform method is only invoked when the inputs to pipe's transform method change. If the pipe has internal state (it's result are dependant on state other than its arguments) than set pure to false so that the pipe is invoked on each change-detection even if the arguments to the pipe do not change.

大意是 pipe 僅在輸入發生改變的時候會再次執行 transform 方法。可是若是 pipe 有一個內部狀態,而且輸出依賴於這個內部狀態,那麼將 pure 設置爲 false 以便在每次 change-detection (數據更新檢測) 時候執行 transform即便輸入的數據並無改變。gulp

對於pure, 默認值爲 true ,若是設置爲 false ,則能夠將上面的功能實現出來。可是同時要注意一個問題,一個 impurepipe (即 pure = false ) 會常常被調用,若是有大量運算,則可能影響用戶體驗 。數組

一樣看一段官網的解釋:

Angular executes an impure pipe during every component change detection cycle. An impure pipe is called often, as often as every keystroke or mouse-move.

對於 impurepipeangular 會在每次 component 的 變化檢測週期裏調用執行,甚至一次按鍵、一次鼠標事件都會觸發 pipe 的執行。

最終,對於上述的問題,只需修改 pipe 的代碼便可。

@Pipe({
    name: 'defaultPure',
    pure: false
})

export class DefaultPurePipe implements PipeTransform {
    transform(feer:any): string {
        return feer.skills.map((v)=>v.name).join(',');
    }
}

完結

對於上述問題,表面上來講加一下 pure 便可,對於深層次來講,涉及到了 angular 內部的一些工做原理。可是話說回來,目前僅僅研究到這種方案來處理,實際上應該還會有其餘的解決方案,大膽猜想能夠利用 change-detection 的一些鉤子來處理,待研究好了再記錄一下。

以前在遇到這個問題的時候,也是一臉懵逼的找答案,卻不知答案就在文檔上寫的清清楚楚。 🤷‍♀️~~~

BTW,其實在 vue 中也存在了一個相似的問題, 爲了性能等的考慮,不會對複合對象作深度變動檢測。而 Vue的作法則簡單粗暴一些:從新裝飾數組方法。 具體能夠查看 Vue源代碼

相關文章
相關標籤/搜索