自定義指令、計算屬性、偵聽器

四、自定義指令

鉤子函數

一個指令定義對象可以提供如下幾個鉤子函數 (均為可選):

  • bind:只調用一次,指令第一次綁定到元素時調用。在這里可以進行一次性的初始化設置。
  • inserted:被綁定元素插入父節點時調用 (僅保證父節點存在,但不一定已被插入文檔中)。
  • update:所在組件的 VNode 更新時調用,但是可能發生在其子 VNode 更新之前。指令的值可能發生了改變,也可能沒有。但是你可以通過比較更新前后的值來忽略不必要的模板更新 (詳細的鉤子函數參數見下)。
  • componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調用。
  • unbind:只調用一次,指令與元素解綁時調用。

鉤子函數參數

指令鉤子函數會被傳入以下參數:

  • el:指令所綁定的元素,可以用來直接操作 DOM 。

  • binding

    :一個對象,包含以下屬性:

    • name:指令名,不包括 v- 前綴。
    • value:指令的綁定值,例如:v-my-directive="1 + 1" 中,綁定值為 2。
    • oldValue:指令綁定的前一個值,僅在 updatecomponentUpdated 鉤子中可用。無論值是否改變都可用。
    • expression:字符串形式的指令表達式。例如 v-my-directive="1 + 1" 中,表達式為 "1 + 1"。
    • arg:傳給指令的參數,可選。例如 v-my-directive:foo 中,參數為 "foo"。
    • modifiers:一個包含修飾符的對象。例如:v-my-directive.foo.bar 中,修飾符對象為 { foo: true, bar: true }
  • vnode:Vue 編譯生成的虛擬節點。移步 VNode API 來了解更多詳情。

  • oldVnode:上一個虛擬節點,僅在 updatecomponentUpdated 鉤子中可用。

通過自定義私有指令,改變nav的背景顏色。

<nav v-demo:[background]="color">
 data() {
    return {
      background:'background',
      color:'#f00',
    };
  },
  directives:{
    demo:{
      bind(el,binding,vnode) {
        el.style[binding.arg] = binding.value;
      }
    }
  },

自定義全局指令

    Vue.directive('color', {
      // 樣式,只要通過指令綁定給了元素,不管這個元素有沒有被插入到頁面中去,這個元素肯定有了一個內聯的樣式
      // 將來元素肯定會顯示到頁面中,這時候,瀏覽器的渲染引擎必然會解析樣式,應用給這個元素
      bind: function (el, binding) {
        // 和樣式相關的操作,一般都可以在 bind 執行。
        el.style.color = binding.value
      }
    })

七、計算屬性

模板內的表達式非常便利,但是設計它們的初衷是用于簡單運算的。在模板中放入太多的邏輯會讓模板過重且難以維護。例如:

<div id="example">
  {{ message.split('').reverse().join('') }}
</div>

在這個地方,模板不再是簡單的聲明式邏輯。你必須看一段時間才能意識到,這里是想要顯示變量 message 的翻轉字符串。當你想要在模板中多次引用此處的翻轉字符串時,就會更加難以處理。

所以,對于任何復雜邏輯,你都應當使用計算屬性。

基礎例子

<div id="example">
  <p>Original message: "{{ message }}"</p>
  <p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
var vm = new Vue({
  el: '#example',
  data: {
    message: 'Hello'
  },
  computed: {
    // 計算屬性的 getter
    reversedMessage: function () {
      // `this` 指向 vm 實例
      return this.message.split('').reverse().join('')
    }
  }
})

結果:

Original message: "Hello"

Computed reversed message: "olleH"

這里我們聲明了一個計算屬性 reversedMessage。我們提供的函數將用作屬性 vm.reversedMessage 的 getter 函數:

console.log(vm.reversedMessage) // => 'olleH'
vm.message = 'Goodbye'
console.log(vm.reversedMessage) // => 'eybdooG'

你可以打開瀏覽器的控制臺,自行修改例子中的 vm。vm.reversedMessage 的值始終取決于 vm.message 的值。

你可以像綁定普通屬性一樣在模板中綁定計算屬性。Vue 知道 vm.reversedMessage 依賴于 vm.message,因此當 vm.message 發生改變時,所有依賴 vm.reversedMessage 的綁定也會更新。而且最妙的是我們已經以聲明的方式創建了這種依賴關系:計算屬性的 getter 函數是沒有副作用 (side effect) 的,這使它更易于測試和理解。

計算屬性緩存 vs 方法

你可能已經注意到我們可以通過在表達式中調用方法來達到同樣的效果:

<p>Reversed message: "{{ reversedMessage() }}"</p>
// 在組件中
methods: {
  reversedMessage: function () {
    return this.message.split('').reverse().join('')
  }
}

我們可以將同一函數定義為一個方法而不是一個計算屬性。兩種方式的最終結果確實是完全相同的。然而,不同的是計算屬性是基于它們的響應式依賴進行緩存的。只在相關響應式依賴發生改變時它們才會重新求值。這就意味著只要 message 還沒有發生改變,多次訪問 reversedMessage計算屬性會立即返回之前的計算結果,而不必再次執行函數。

這也同樣意味著下面的計算屬性將不再更新,因為 Date.now() 不是響應式依賴:

computed: {
  now: function () {
    return Date.now()
  }
}

相比之下,每當觸發重新渲染時,調用方法將總會再次執行函數。

我們為什么需要緩存?假設我們有一個性能開銷比較大的計算屬性 A,它需要遍歷一個巨大的數組并做大量的計算。然后我們可能有其他的計算屬性依賴于 A 。如果沒有緩存,我們將不可避免的多次執行 A 的 getter!如果你不希望有緩存,請用方法來替代。

computed

  • 類型{ [key: string]: Function | { get: Function, set: Function } }

  • 詳細

    計算屬性將被混入到 Vue 實例中。所有 getter 和 setter 的 this 上下文自動地綁定為 Vue 實例。

    注意如果你為一個計算屬性使用了箭頭函數,則 this 不會指向這個組件的實例,不過你仍然可以將其實例作為函數的第一個參數來訪問。

    computed: {
      aDouble: vm => vm.a * 2
    }
    

    計算屬性的結果會被緩存,除非依賴的響應式屬性變化才會重新計算。注意,如果某個依賴 (比如非響應式屬性) 在該實例范疇之外,則計算屬性是不會被更新的。

  • 示例

    var vm = new Vue({
      data: { a: 1 },
      computed: {
        // 僅讀取
        aDouble: function () {
          return this.a * 2
        },
        // 讀取和設置
        aPlus: {
          get: function () {
            return this.a + 1
          },
          set: function (v) {
            this.a = v - 1
          }
        }
      }
    })
    vm.aPlus   // => 2
    vm.aPlus = 3
    vm.a       // => 2
    vm.aDouble // => 4
    
計算屬性的 setter

計算屬性默認只有 getter ,不過在需要時你也可以提供一個 setter :

// ...
computed: {
  fullName: {
    // getter
    get: function () {
      return this.firstName + ' ' + this.lastName
    },
    // setter
    set: function (newValue) {
      var names = newValue.split(' ')
      this.firstName = names[0]
      this.lastName = names[names.length - 1]
    }
  }
}
// ...

現在再運行 vm.fullName = 'John Doe' 時,setter 會被調用,vm.firstNamevm.lastName也會相應地被更新。

監聽 watch

  • 類型{ [key: string]: string | Function | Object | Array }

  • 詳細

    一個對象,鍵是需要觀察的表達式,值是對應回調函數。值也可以是方法名,或者包含選項的對象。Vue 實例將會在實例化時調用 $watch(),遍歷 watch 對象的每一個屬性。

  • 示例

    var vm = new Vue({
      data: {
        a: 1,
        b: 2,
        c: 3,
        d: 4,
        e: {
          f: {
            g: 5
          }
        }
      },
      watch: {
        a: function (val, oldVal) {
          console.log('new: %s, old: %s', val, oldVal)
        },
        // 方法名
        b: 'someMethod',
        // 該回調會在任何被偵聽的對象的 property 改變時被調用,不論其被嵌套多深
        c: {
          handler: function (val, oldVal) { /* ... */ },
          deep: true
        },
        // 該回調將會在偵聽開始之后被立即調用
        d: {
          handler: 'someMethod',
          immediate: true
        },
        e: [
          'handle1',
          function handle2 (val, oldVal) { /* ... */ },
          {
            handler: function handle3 (val, oldVal) { /* ... */ },
            /* ... */
          }
        ],
        // watch vm.e.f's value: {g: 5}
        'e.f': function (val, oldVal) { /* ... */ }
      }
    })
    vm.a = 2 // => new: 2, old: 1
    

    注意,不應該使用箭頭函數來定義 watcher 函數 (例如 searchQuery: newValue => this.updateAutocomplete(newValue))。理由是箭頭函數綁定了父級作用域的上下文,所以 this 將不會按照期望指向 Vue 實例,this.updateAutocomplete 將是 undefined。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容