Vue 3.0 自定義指令

2022-03-07 09:44 更新

#簡介

除了核心功能默認(rèn)內(nèi)置的指令 (v-modelv-show),Vue 也允許注冊自定義指令。注意,在 Vue2.0 中,代碼復(fù)用和抽象的主要形式是組件。然而,有的情況下,你仍然需要對普通 DOM 元素進(jìn)行底層操作,這時(shí)候就會用到自定義指令。舉個(gè)聚焦輸入框的例子,如下:

點(diǎn)擊此處實(shí)現(xiàn)

當(dāng)頁面加載時(shí),該元素將獲得焦點(diǎn) (注意:autofocus 在移動(dòng)版 Safari 上不工作)。事實(shí)上,只要你在打開這個(gè)頁面后還沒點(diǎn)擊過任何內(nèi)容,這個(gè)輸入框就應(yīng)當(dāng)還是處于聚焦?fàn)顟B(tài)。此外,你可以單擊 Rerun 按鈕,輸入將被聚焦。

現(xiàn)在讓我們用指令來實(shí)現(xiàn)這個(gè)功能:

const app = Vue.createApp({})
// 注冊一個(gè)全局自定義指令 `v-focus`
app.directive('focus', {
  // 當(dāng)被綁定的元素插入到 DOM 中時(shí)……
  mounted(el) {
    // Focus the element
    el.focus()
  }
})

如果想注冊局部指令,組件中也接受一個(gè) directives 的選項(xiàng):

directives: {
  focus: {
    // 指令的定義
    mounted(el) {
      el.focus()
    }
  }
}

然后你可以在模板中任何元素上使用新的 v-focus property,如下:

<input v-focus />

#鉤子函數(shù)

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

  • beforeMount:當(dāng)指令第一次綁定到元素并且在掛載父組件之前調(diào)用。在這里你可以做一次性的初始化設(shè)置。
  • mounted:在掛載綁定元素的父組件時(shí)調(diào)用。
  • beforeUpdate:在更新包含組件的 VNode 之前調(diào)用。

提示

我們會在稍后討論渲染函數(shù)時(shí)介紹更多 VNodes 的細(xì)節(jié)。

  • updated:在包含組件的 VNode 及其子組件的 VNode 更新后調(diào)用。
  • beforeUnmount:在卸載綁定元素的父組件之前調(diào)用
  • unmounted:當(dāng)指令與元素解除綁定且父組件已卸載時(shí),只調(diào)用一次。

接下來我們來看一下在自定義指令 API 鉤子函數(shù)的參數(shù) (即 el、bindingvnodeprevNnode)

#動(dòng)態(tài)指令參數(shù)

指令的參數(shù)可以是動(dòng)態(tài)的。例如,在 v-mydirective:[argument]="value" 中,argument 參數(shù)可以根據(jù)組件實(shí)例數(shù)據(jù)進(jìn)行更新!這使得自定義指令可以在應(yīng)用中被靈活使用。

例如你想要?jiǎng)?chuàng)建一個(gè)自定義指令,用來通過固定布局將元素固定在頁面上。我們可以像這樣創(chuàng)建一個(gè)通過指令值來更新豎直位置像素值的自定義指令:

<div id="dynamic-arguments-example" class="demo">
  <p>Scroll down the page</p>
  <p v-pin="200">Stick me 200px from the top of the page</p>
</div>

const app = Vue.createApp({})


app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.value is the value we pass to directive - in this case, it's 200
    el.style.top = binding.value + 'px'
  }
})


app.mount('#dynamic-arguments-example')

這會把該元素固定在距離頁面頂部 200 像素的位置。但如果場景是我們需要把元素固定在左側(cè)而不是頂部又該怎么辦呢?這時(shí)使用動(dòng)態(tài)參數(shù)就可以非常方便地根據(jù)每個(gè)組件實(shí)例來進(jìn)行更新。

<div id="dynamicexample">
  <h3>Scroll down inside this section ↓</h3>
  <p v-pin:[direction]="200">I am pinned onto the page at 200px to the left.</p>
</div>

const app = Vue.createApp({
  data() {
    return {
      direction: 'right'
    }
  }
})


app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    // binding.arg is an argument we pass to directive
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})


app.mount('#dynamic-arguments-example')

結(jié)果:

點(diǎn)擊此處實(shí)現(xiàn)

我們的定制指令現(xiàn)在已經(jīng)足夠靈活,可以支持一些不同的用例。為了使其更具動(dòng)態(tài)性,我們還可以允許修改綁定值。讓我們創(chuàng)建一個(gè)附加屬性 pinPadding,并將其綁定到 <input type="range">。

<div id="dynamicexample">
  <h2>Scroll down the page</h2>
  <input type="range" min="0" max="500" v-model="pinPadding">
  <p v-pin:[direction]="pinPadding">Stick me {{ pinPadding + 'px' }} from the {{ direction }} of the page</p>
</div>

const app = Vue.createApp({
  data() {
    return {
      direction: 'right',
      pinPadding: 200
    }
  }
})

讓我們擴(kuò)展我們的指令邏輯來重新計(jì)算固定元件更新的距離。

app.directive('pin', {
  mounted(el, binding) {
    el.style.position = 'fixed'
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  },
  updated(el, binding) {
    const s = binding.arg || 'top'
    el.style[s] = binding.value + 'px'
  }
})

結(jié)果:

點(diǎn)擊此處實(shí)現(xiàn)

#函數(shù)簡寫

在很多時(shí)候,你可能想在 mountedupdated 時(shí)觸發(fā)相同行為,而不關(guān)心其它的鉤子。比如這樣寫:

app.directive('pin', (el, binding) => {
  el.style.position = 'fixed'
  const s = binding.arg || 'top'
  el.style[s] = binding.value + 'px'
})

#對象字面量

如果指令需要多個(gè)值,可以傳入一個(gè) JavaScript 對象字面量。記住,指令函數(shù)能夠接受所有合法的 JavaScript 表達(dá)式。

<div v-demo="{ color: 'white', text: 'hello!' }"></div>

app.directive('demo', (el, binding) => {
  console.log(binding.value.color) // => "white"
  console.log(binding.value.text) // => "hello!"
})

#在組件中使用

在 3.0 中,有了片段支持,組件可能有多個(gè)根節(jié)點(diǎn)。如果在具有多個(gè)根節(jié)點(diǎn)的組件上使用自定義指令,則會產(chǎn)生問題。

要解釋自定義指令如何在 3.0 中的組件上工作的詳細(xì)信息,我們首先需要了解自定義指令在 3.0 中是如何編譯的。對于這樣的指令:

<div v-demo="test"></div>

將大概編譯成:

const vDemo = resolveDirective('demo')


return withDirectives(h('div'), [[vDemo, test]])

其中 vDemo 是用戶編寫的指令對象,其中包含 mountedupdated 等鉤子。

withDirectives 返回一個(gè)克隆的 VNode,其中用戶鉤子被包裝并作為 VNode 生命周期鉤子注入 (請參見渲染函數(shù)更多詳情):

{
  onVnodeMounted(vnode) {
    // call vDemo.mounted(...)
  }
}

因此,自定義指令作為 VNode 數(shù)據(jù)的一部分完全包含在內(nèi)。當(dāng)在組件上使用自定義指令時(shí),這些 onVnodeXXX 鉤子作為無關(guān)的 prop 傳遞給組件,并以 this.$attrs 結(jié)束

這也意味著可以像這樣在模板中直接掛接到元素的生命周期中,這在涉及到自定義指令時(shí)非常方便:

<div @vnodeMounted="myHook" />

這和 非 prop 的 attribute類似。因此,組件上自定義指令的規(guī)則將與其他無關(guān) attribute 相同:由子組件決定在哪里以及是否應(yīng)用它。當(dāng)子組件在內(nèi)部元素上使用 v-bind="$attrs" 時(shí),它也將應(yīng)用對其使用的任何自定義指令。

以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號