你也許不知道的Vuejs - 強大的指令系統

yugasun發表於2018-02-05

by yugasun from yugasun.com/post/you-ma… 本文可全文轉載,但需要保留原作者和出處。

Vuejs 中,指令(Directives)是帶有 v- 字首的特殊屬性。指令屬性的預期值是 單個 Javascript 表示式v-for 是個例外)。指令的職責是,當表示式改變時,將其產生的連帶影響,響應式的作用於 DOM。

當然,Vue 除了核心功能預設內建的指令外,更強大的是它允許註冊自定義指令,這是個讓我非常驚喜功能。因為當初在使用 Angular1.x 時就特別喜好自定義指令這個功能,沒想到 Vue 也借鑑了進來,讓我能夠靈活對 DOM 進行底層操作,而且它具有非常高的複用性,書寫起來也非常簡潔。

當然在 Vue 中,程式碼的複用和抽象的主要形式還是 元件,這將在下一節中講到。

內建指令

Vue 提供供了大概 13 種內建指令,基本上能夠滿足我們的日常開發需求,不得不說作者考慮的已經非常全面,給尤大大點贊。這裡只介紹 v-forv-on 兩種內建指令,感興趣的可以到 官方API 瞭解。

1.v-for

上一節我們已經使用過了,它是基於源資料多次渲染元素或莫板塊的。此指令的值,必須使用特定語法 alias in expression 來進行輸出。 expression 為需要遍歷的物件,它可以是 Array | Object | number | string 四種型別,alias 為遍歷的元素別名。這個跟 js 的遍歷函式 map 很相似,先來看個例子:

<div id="app1">
  <ul>
    <li v-for="item in 5">
      {{ item }}
    </li>
  </ul>
</div>
複製程式碼
var app1 = new Vue({
  el: "#app1",
})
複製程式碼

這裡是在頁面上依次輸出 1~5 的數字,此時 expression5number(number 必須為正整數) 型別。 Vue 在渲染時,會先建立一個長度為 5 的陣列,然後遍歷輸出 item + 1 的值。如果為 string 型別,就會建立一個該字串長度的陣列,再遍歷輸出字串的每個字元。單純講肯定不太好理解,其實這部分原始碼很簡單,大家可以直接通過閱讀來理解:官方原始碼

大多數時候,expression 都只是個陣列,其實知道它還可以為 string 或者 number 是很有用的,比如我們需要快速重複輸出一個DOM,使用 number 能很快實現,節約了我們複製貼上或者書寫陣列列表的時間。當為 string 時,我們可以根據需求依次輸出每個字元,以此可以做出很炫酷的打字效果,如下:

<div id="app2">
  <span v-for="(item, index) in name" v-bind:style="{animationDelay: `${0.5 + index * 0.3}s`}">{{ item }}</span>
</div>
複製程式碼
#app2 span {
  animation: flip-in 1s 0s ease-in-out both;
}
@keyframes flip-in {
  0% {
    opacity: 0;
  }
  100% {
    opacity: 1;
  }
}
複製程式碼
// demo 2
var app2 = new Vue({
  el: "#app2",
  data () {
    return {
      name: 'yugasun'
    }
  }
})
複製程式碼

怎麼樣?曾今看到別人做出炫酷的打字效果簡歷是不是很羨慕,這裡只需要20行程式碼就可以實現打字效果,趕緊動手試試吧~

注意: 這裡在設定每個字元的 animation-delay 屬性時,用到了 v-bind 指令,這個很簡單,這裡不做贅述。同時使用了 ES6 的模板字元語法來計算時延的值,如果瀏覽器不支援,建議更換為新版 Chrome 瀏覽器。模板字串 教程在這裡。

2.v-on

v-on 指令是用來繫結事件監聽器的,事件型別可以是DOM原生事件,也可以是通過 $emit 觸發的自定義事件。但是隻有用在自定義元素元件上時,才可以監聽元件觸發的自定義事件。

在監聽原生 DOM 事件時,方法以事件為唯一的引數。如果使用內聯語句,語句可以訪問一個 $event 屬性:v-on:click="handle('ok', $event)"

看程式碼更好理解:

<div id="app3">
  <button v-on:click="handleClick('確定', $event)">確定</button>
  <button v-on:click="handleClick('取消', $event)">取消</button><br>
  {{ msg }}
</div>
複製程式碼
var app3 = new Vue({
  el: "#app3",
  data () {
    return {
      btnText: ''
    }
  },
  computed: {
    msg () {
      return this.btnText ? `點選了 ${this.btnText} 按鈕` : '您還沒點選任何按鈕'
    }
  },
  methods: {
    handleClick (text, e) {
      console.log(e)
      this.btnText = text
    }
  }
})
複製程式碼

這裡通過 v-on 監聽了兩個按鈕的點選事件,並分別傳遞了各自的文字到監聽函式進行輸出,輸出的 msg 是計算屬性(如果不懂什麼是計算屬性,教程在這裡 深入淺出響應式系統),依賴 btnText 動態計算的。可以通過控制檯檢視列印出的引數 e 物件就是一個原生 MouseEvent 物件。

除了基本的用法, v-on 指令還提供了豐富的 修飾符 來配合使用,這樣可以寫出非常靈活的監聽事件,比如我們經常使用的 e.preventDefault(),在 Vue 中我們只需要通過 v-on:click.prevent 就可以實現了,是不是很炫酷,所有的修飾符列表在 這裡。這裡我們來演示下,點選滑鼠左鍵和右鍵的事件監聽,將上面的 html 程式碼修改如下:

<div id="app3">
  <!-- 相對於上面只新增了 `.left` 和 `.right` 修飾符 -->
  <button v-on:click.left="handleClick('確定', $event)">確定</button>
  <button v-on:click.right="handleClick('取消', $event)">取消</button><br>
  {{ msg }}
</div>
複製程式碼

自定義指令

最讓我激動人心的就是自定義指令了,它即可以通過 Vue.directive 註冊全域性指令,也可以給 Vue 例項新增 directives 屬性來註冊區域性指令。這兩種指令都是通過指令定義時的鉤子函式實現的。

關於鉤子函式和鉤子函式引數介紹請直接閱讀官方文件,官方文件已經講述的非常詳細了,這裡不再贅述,直通車:鉤子函式鉤子函式引數

相信你們已經很快看完了官方文件介紹了,好了現在我將帶你來領略自定義指令的強大,實現一個倒數計時指令。

鉤子函式 bind 是在元素第一次繫結到元素時呼叫的,所以我們可以在這裡初始化繫結的元素 el,而且繫結元素已經作為所有鉤子函式的第一個引數被傳入,這裡可以通過 el.innerHTML 就很容易修改繫結元素的內容了,程式碼如下:

<div id="app4">
  <button v-on:click="startCount">開始倒數計時</button>
  <span data-count="60" v-count-down="count" v-if="show" style="font-size:16px;"></span>
</div>
複製程式碼
var app4 = new Vue({
  el: '#app4',
  data () {
    return {
      show: false, // 是否顯示倒數計時
      timer: null // 定時器
    }
  },
  methods: {
    startCount: function () {
      this.show = true
    }
  },
  directives: {
    'count-down': {
      bind: function (el, binding, vnode) {
        el.innerHTML = '60'
      }
    }
  }
})
複製程式碼

當我們點選按鈕是,將 show 置為 true,此時 v-if 指令就判斷顯示使用 v-count-down 指令的元素,然後執行 bind 初始化函式,將元素內容修改為 60。這裡很好理解。但是我們要實現倒數計時,就需要建立一個定時器,並需要維護一個數實現遞減,這裡通過動態修改 data-count 的值來實現。

實現思路:當初始化時,建立一個定時器,每隔1s將獲取 data-count 的值減一,然後賦給 el.innerHTML,同時修改 data-count 屬性為新的值,當然還需要新增判斷,就是當然 data-count 為0時,清除計時器。

有了思路,實現程式碼就非常簡單了:

<div id="app4">
  <button v-on:click="startCount">開始倒數計時</button>
  <span v-count-down data-count="60" v-if="show" style="font-size:16px;">
    60
  </span>
</div>
複製程式碼
var app4 = new Vue({
  el: '#app4',
  data () {
    return {
      show: false, // 是否顯示倒數計時
    }
  },
  methods: {
    startCount: function () {
      this.show = true
    }
  },
  directives: {
    'count-down': {
      bind: function (el, binding, vnode) {
        let count = parseInt(el.getAttribute('data-count'))
        if (this.timer) {
          clearInterval(this.timer)
          this.timer = null
        }
        // 大家可以推測下這裡的 this 指什麼?
        this.timer = setInterval(function () {
          if (count <= 0) {
            clearInterval(this.timer)
            this.timer = null
          } else {
            count--
            el.innerHTML = count
            el.setAttribute('data-count', count)
          }
        }, 1000)
      }
    }
  }
})
複製程式碼

指令系統就介紹到這裡,最後留給大家一個問題:

上面程式碼中有個 this.timer, 那麼這個 this 指的是什麼呢?如果我們需要將這個 timercount 變數 維護在當前 Vue 例項中,該如何做呢?

原始碼在此

專題目錄

You-May-Not-Know-Vuejs

相關文章