Skip to content
On this page

directive 自定義指令 v-*

簡單說

可以自定義像 v-if 這樣的指令,有「組件註冊」、「全域註冊」兩種方式。

說明

vue 中常見的 v-showv-if 都是原生的自定義指令,也可以自已定義專用的指令。

vue
<template>
  <div>
    <h1 v-show="false">Directive Demo</h1>
  </div>
</template>

組件註冊

在組件定義,只可以在組件內被使用,無法跟組件使用。

註冊指令

option 方法中,可以使用 directives 的選項來註冊,將名稱寫入索引。

js
export default {
  data: () => ({}),

  directives: {
    focus: {
      inserted(el) {
        // 元素聚焦
        el.focus()
      },
    },
  },
}

使用指令

directive 中註冊的名稱加上前綴 v- 使用 (比如 v-名稱)。

vue
<template>
  <div>
    <span>Auto focus </span>
    <input v-focus />
  </div>
</template>

全域註冊

當使用「全域」註冊,在任何組件都可以使用,不會受限跨組件使用。

註冊指令

要在入口 main.js 直接註冊在 vue 實體上。

~/src/main.js

js
import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

// 向定義指令註冊
Vue.directive('force', {
  inserted(el) {
    // 元素聚焦
    el.focus()
  },
})

new Vue({
  render: (h) => h(App),
}).$mount('#app')

使用指令

directive 註冊的名稱加上前綴 v- 使用 (比如 v-名稱)。

vue
<template>
  <div>
    <span>Auto focus by Global</span>
    <input v-force />
  </div>
</template>

指令 hook 函式

自定義指令,提供了很多操作的階段,可以參考:

hook說明
binddirective 初次綁定到元素時,執行的函式 (只執行一次),一般可以在此做 初始化設置。
inserted當綁定的元素被插入 父節點 時,執行的函式。(只保証父節點存在,綁定的元素不一定在 dom 中)
update組件傳送數據更新時執行函式 (可能發生在綁定元素其子元素更新之前)。
componentUpdated所在組件的與其子元素全部更新後執行的函式。
unbind解除綁定元素時執行函式。(元素不存在)

函式參數:

參數說明 / 子項目子項目說明
el綁定的元素 DOM--
binding綁定元素的內容對象--
--name指令名稱 (不包含 v- )
--rawName自定義指令名稱 v-* )
--value傳遞的數據 ex: v-focus="1 + 1" 則為 2
--expression原始寫在屬性上,傳遞的表達式 ex: v-focus="value + 1" 則為 value + 1
--arg傳遞指令參數 的數據 ex: v-focus:passData 則為 "passData";若要傳遞「動態」數據,加入 [] ex v-focus:[value] (要在修飾符之前)
--modifiers指令修飾符,可以拿來判斷是否執行是某個操作 ex v-template.typeA.typeB 則為 { typeA: true, typeB: true }
vnodeVNode ( 虛擬 DOM )--
oldVnode上一個 VNode ( 虛擬 DOM )--
CODE
vue
<template>
  <div>
    <input v-focus="1 + 1" :value="value" />
  </div>
</template>

<script>
export default {
  data: () => ({
    value: 1,
  }),

  directives: {
    focus: {
      inserted(el, binding) {
        console.log(binding)
        // {
        //   name: 'focus',
        //   rawName: 'v-focus',
        //   value: 2,
        //   expression: '1 + 1',
        //   modifiers: Object,
        //   def: {...}
        // }
      },
    },
  },
}
</script>

實務應用

圖像應用

新增一個指令 v-img 來判斷圖像的載入中、若失效、載入完成,的相關操作應用。

vue
<template>
  <div>
    <img v-img src="null" />
  </div>
</template>

<script>
export default {
  directives: {
    img: {
      inserted(el) {
        console.log(el.src)
        const img = new Image()
        img.src = el.src

        // 圖片載入中
        el.src =
          'https://www.freeiconspng.com/thumbs/no-image-icon/no-image-icon-6.png'

        // 如果圖片載入
        img.onload = () => {
          console.log('img load')
          el.src = img.src
        }

        // 如果圖片失效
        img.onerror = () => {
          console.log('img onError')
          el.src = 'https://www.freeiconspng.com/uploads/error-icon-28.png'
        }
      },
    },
  },
}
</script>

定位綁定

利用自定義傳參,來變更元素定位方式,input type="range" 改變定位數值。

vue
<template>
  <div>
    <div v-position="{ position, value }" class="fixed red">Position</div>
    <button @click="positionBtn('top')">Top</button>
    <button @click="positionBtn('bottom')">Bottom</button>
    <button @click="positionBtn('right')">Right</button>
    <button @click="positionBtn('left')">Left</button>
    <input type="range" v-model="value" />
  </div>
</template>

<script>
export default {
  directives: {
    position: {
      // 初始化
      inserted(el, binding) {
        el.style[binding.value.position] = `${binding.value.value}%`
      },

      // 數據更改時
      update(el, binding) {
        console.log('update')
        if (
          binding.value.position === 'top' ||
          binding.value.position === 'bottom'
        ) {
          el.style.top = ''
          el.style.bottom = ''
        }
        if (
          binding.value.position === 'right' ||
          binding.value.position === 'left'
        ) {
          el.style.left = ''
          el.style.right = ''
        }
        el.style[binding.value.position] = `${binding.value.value}%`
      },
    },
  },

  data: () => ({
    position: 'top',
    value: 0,
  }),

  methods: {
    positionBtn(type) {
      this.position = type
      this.value = 0
    },
  },
}
</script>

<style lang="css" scoped>
.fixed {
  position: fixed;
}
.red {
  background: red;
  padding: 1rem;
  border-radius: 0.5rem;
}
</style>

Reference