高階 vue 元件模式 6

HaoliangWu發表於2018-10-21

06 通過 directive 增強元件內容

目標

之前的五篇文章中,switch 元件一直是被視為內部元件存在的,細心的讀者應該會發現,這個元件除了幫我們提供開關的互動以外,還會根據當前 toggle 的開關狀態,為 button 元素增加 aria-expanded 屬性,以 aira 開頭的屬性叫作內容增強屬性,它用於描述當前元素的某種特殊狀態,幫助殘障人士更好地瀏覽網站內容。

但是,作為元件呼叫者,未必會對使用這種相關屬性對網站內容進行增強,那麼如何更好地解決這個問題呢?答案就是使用 directive。

我們期望能夠顯示地宣告當前的元素是一個 toggler 職能的元件或者元素,這個元件或者元素,可以根據當前 toggle 元件的開關狀態,動態地更新它本身的 aria-expanded 屬性,以便針對無障礙訪問提供適配。

實現

簡單實現

首先建立一個 toggler 指令函式,如下:

export default function(el, binding, vnode) {
  const on = binding.value

  if (on) {
    el.setAttribute(`aria-expanded`, true);
  } else {
    el.removeAttribute(`aria-expanded`, false);
  }
}
複製程式碼

這個指令函式很簡單,就是通過傳入指令的表示式的值來判定,是否在當前元素上增加一個 aria-expanded 屬性。之後再 app 引入該指令,如下:

directives: {
  toggler
}
複製程式碼

之後就可以在 app 元件的模板中使用該指令了,比如:

<custom-button v-toggler="status.on" ref="customButton" :on="status.on" :toggle="toggle"></custom-button>
複製程式碼

一切都將按預期中執行,當 toggle 元件的狀態為開時,custom-button 元件的根元素會增加一個 aria-expanded="true" 的內容增強屬性。

Note: 這裡關於指令的引入,使用的函式簡寫的方式,會在指令的 bind 和 update 鉤子函式中觸發相同的邏輯,vue 中的指令包含 5 個不同的鉤子函式,這裡就不贅述了,不熟悉的讀者可以通過閱讀官方文件來了解。

注入當前元件例項

上文中的指令會通過 binding.value 來獲取 toggle 元件的開關狀態,這樣雖然可行,但在使用該指令時,custom-button 本身的 prop 屬性 on 已經代表了當前的開關狀態,能否直接在指令中獲取當前所繫結的元件例項呢?答案是可以的。指令函式的第三個引數即為當前所繫結元件的虛擬 dom 節點例項,其 componentInstance 屬性指向當前元件例項,所以可以將之前的指令改版如下:

export default function(el, binding, vnode) {
  const comp = vnode.componentInstance;
  const on = binding.value || comp.on;

  if (on) {
    el.setAttribute(`aria-expanded`, true);
  } else {
    el.removeAttribute(`aria-expanded`, false);
  }
}
複製程式碼

這樣,即使不向指令傳入表示式,它也可以自動去注入當前修飾元件所擁有的 prop 屬性 on 的值,如下:

<custom-button v-toggler ref="customButton" :on="status.on" :toggle="toggle"></custom-button>
複製程式碼

提供更多靈活性

指令函式的第二個引數除了可以獲取傳入指令內部的表示式的值以外,還有其他若干屬性,比如 name、arg、modifiers等,詳細說明可以去參考官方文件。

為了儘可能地使指令保證靈活性,我們期望可以自定義無障礙屬性 aria 的字尾名稱,比如叫做 aria-on,這裡我們可以通過 arg 這個引數輕鬆實現,改版如下:

export default function(el, binding, vnode) {
  const comp = vnode.componentInstance;
  const suffix = binding.arg || "expanded";
  const on = binding.value || comp.on;

  if (on) {
    el.setAttribute(`aria-${suffix}`, true);
  } else {
    el.removeAttribute(`aria-${suffix}`, false);
  }
}
複製程式碼

可以發現,這裡通過 binding.arg 來獲取無障礙屬性的字尾名稱,並當沒有傳遞該引數時,降級至 expanded。這裡僅僅是為了演示,讀者有興趣的話,還可以利用 binding 物件的其他屬性提供更多的靈活性。

成果

最終的執行結果就不用語言描述了,直接截了一個圖,是 toggle 元件開關狀態為開時的截圖:

高階 vue 元件模式 6

你可以下面的連結來看看這個元件的實現程式碼以及演示:

總結

關於指令的概念,我自身還是在 angularjs(v1.2以下版本) 中第一次接觸,當時其實不興元件化開發這個概念,指令本身的設計理念也是基於增強這個概念的,即增強某個 html 標籤。到後來興起了元件化開發的開發思想,指令似乎是隨著 angularjs 的沒落而消失了蹤影。

但仔細想想的話,web 開發流程中,並不是所有的場景都可以拿元件來抽象和描述的,比如說,你想提供一個類似高亮邊框的公用功能,到底如何來按元件化的思想抽象它呢?這時候使用指令往往是一個很好的切入點。

因此,當你面臨解決的問題,顆粒度小於元件化抽象的粒度,同時又具備複用性,那就大膽的使用指令來解決它吧。

目錄

github gist

相關文章