亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

如何在Vue3中實(shí)現(xiàn)自定義指令(超詳細(xì)!)

 更新時(shí)間:2022年06月11日 07:44:18   作者:諸葛小愚  
除了默認(rèn)設(shè)置的核心指令(v-model和v-show),Vue也允許注冊(cè)自定義指令,下面這篇文章主要給大家介紹了關(guān)于如何在Vue3中實(shí)現(xiàn)自定義指令的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考下

在開(kāi)發(fā)Vue項(xiàng)目時(shí),大多數(shù)人都會(huì)使用到Vue內(nèi)置的一些指令,例如v-model、v-if等,在使用的時(shí)候不知道有沒(méi)有想過(guò)自己也來(lái)實(shí)現(xiàn)一個(gè)指令呢。本文就以Vue3項(xiàng)目為基礎(chǔ),從原理、方法到實(shí)際案例、注意事項(xiàng),盡可能細(xì)致的講解如何自定義指令。

前言

我們需要明白為什么需要自定義一個(gè)指令,其實(shí)就是想更加簡(jiǎn)潔地重復(fù)使用操作DOM的邏輯,這就和組件化和組合式函數(shù)差不多。

不管是Vue內(nèi)置的指令還是自定義的指令,都有類似于組件的生命周期,我們可以在不同的生命周期完成不同的邏輯操作,并綁定到組件元素上,這樣就產(chǎn)生了自定義指令。在Vue3中,我們有三種方式可以定義指令:

  • 如果是在<script setup>定義組件內(nèi)的指令,有一個(gè)語(yǔ)法糖可以使用:任何以v開(kāi)頭的駝峰式命名的變量都可以被用作一個(gè)自定義指令,然后在模板中使用。舉一個(gè)簡(jiǎn)單的例子:在輸入框渲染后自動(dòng)聚焦

    <script setup>
    // 在模板中啟用 v-focus
    const vFocus = {
      mounted: (el) => el.focus()
    }
    </script>
    
    <template>
      <input v-focus />
    </template>

    運(yùn)行效果:

  • 如果是使用選項(xiàng)式,則自定義指令需要在directives選項(xiàng)中注冊(cè)。同上一個(gè)例子:

    <script>
    export default{
      setup() {},
      directives: {
        // 指令名
        focus: {
          // 生命周期
          mounted(el) {
            // 處理DOM的邏輯
            el.focus();
          },
        }
      }
    }
    </script>
    <template>
      <input v-focus />
    </template>

    實(shí)現(xiàn)的效果也是和上一個(gè)例子一樣。

  • 除了注冊(cè)組件內(nèi)指令,我們還可以自定義全局指令,這樣在所有的組件中都可以使用該指令

    // main.js
    import { createApp } from 'vue'
    import App from './App.vue'
    
    const app = createApp(App)
    app.directive('focus', {
      mounted(el) {
        el.focus();
      }
    })
    app.mount('#app')

    實(shí)現(xiàn)效果也是一樣的。

這三種方式我們選擇最后一種,其他兩種方式可以按照類似的方式實(shí)現(xiàn)。

生命周期

指令的生命周期和組件的生命周期類似:

app.directive('focus', {
  created() {
    console.log('created');
  },
  beforeMount() {
    console.log('beforeMount');
  },
  mounted() {
    console.log('mounted');
  },
  beforeUpdate() {
    console.log('beforeUpdate');
  },
  updated() {
    console.log('updated');
  },
  beforeUnmount() {
    console.log('beforeUnmount');
  },
  unmounted() {
    console.log('unmounted');
  }
})

運(yùn)行結(jié)果:

注意指令沒(méi)有beforeCreated鉤子。

  • created:在綁定元素的屬性前,或者事件監(jiān)聽(tīng)器應(yīng)用前調(diào)用
  • beforeMount:在元素被插入到DOM前調(diào)用,例如我們想要實(shí)現(xiàn)輸入框的自動(dòng)聚焦,就不能在beforeMount鉤子中實(shí)現(xiàn)
  • mounted:在綁定元素的父組件以及自己的所有子節(jié)點(diǎn)都掛載完畢后調(diào)用,這個(gè)時(shí)候DOM已經(jīng)渲染出來(lái),我們實(shí)現(xiàn)輸入框自動(dòng)聚焦也是在這個(gè)鉤子函數(shù)中實(shí)現(xiàn)
  • beforeUpdate:綁定元素的父組件更新前調(diào)用
  • updated:在綁定元素的父組件以及自己的所有子節(jié)點(diǎn)都更新完畢后調(diào)用
  • beforeUnmount:綁定元素的父組件卸載前調(diào)用
  • unmounted:綁定元素的父組件卸載后調(diào)用

每個(gè)鉤子函數(shù)都有對(duì)應(yīng)的參數(shù),接下來(lái)繼續(xù)看鉤子參數(shù)。

鉤子的參數(shù)

指令是為了能重用對(duì)DOM的操作邏輯,因此指令參數(shù)可以有1-4個(gè)參數(shù),其中必需的參數(shù)就是當(dāng)前綁定的DOM元素。

語(yǔ)法:

created(el, binding, vnode, preVnode) {}

參數(shù)比較多,我們一個(gè)一個(gè)來(lái)學(xué)習(xí)。

  • el:指令綁定到的DOM元素,可以用于直接操作當(dāng)前元素,默認(rèn)傳入鉤子的就是el參數(shù),例如我們開(kāi)始實(shí)現(xiàn)的focus指令,就是直接操作的元素DOM

  • binding:這是一個(gè)對(duì)象,包含以下屬性:

    • value:在元素上使用指令時(shí),傳遞給指令的值。例如:<div v-reverse="'hello'"></div>,傳遞給reserve指令的值就是hello,我們可以拿到值并做相關(guān)處理
    • oldValue:之前的值,一般用于beforeUpateupdated鉤子函數(shù)中,例如:beforeUpdate(el, {oldValue: ''})
    • arg:傳遞給指令的參數(shù),非必需,例如<div v-reverse:foo="'hello'"></div>,那么傳遞給指令的參數(shù)就是foo
    • modifiers:一個(gè)由修飾符構(gòu)成的對(duì)象,例如<div v-reverse.foo.bar="'hello'"></div>,那么該修飾符對(duì)象為{foo: true, bar: true},我們經(jīng)常使用到的事件修飾符,其實(shí)和這個(gè)差不多。
    • instance:使用該指令的組件實(shí)例,注意不是DOM
    • dir:指令的定義對(duì)象
  • vnode:綁定元素的地城VNode

  • preVnode:之前的渲染中代表指令所綁定的元素的VNode,一般用于beforeUpateupdated鉤子函數(shù)中

可能看這些參數(shù)會(huì)一時(shí)迷糊,我們來(lái)看一個(gè)例子:

定義一個(gè)可翻轉(zhuǎn)輸入框輸入的指令,注意鉤子函數(shù)要選擇beforeUpdate

app.directive('reserve', {
  beforeUpdate(el, binding) {
    console.log(binding);
    el.innerText = binding.value ? binding.value.split('').reverse().join('') : '';
  }
})

在模板中使用:輸入框輸入值,div會(huì)顯示反轉(zhuǎn)后的值

<script setup>
import {ref} from 'vue'
let hello = ref('')
</script>
<template>
  <input v-focus v-model="hello" />
  <div v-reserve:foo.bar="hello"></div>
</template>

運(yùn)行結(jié)果:

結(jié)合該圖,是不是就更能理解鉤子參數(shù)的含義了。

簡(jiǎn)化形式

我們?cè)趯?xiě)指令的時(shí)候,可以具體指定在哪些鉤子中執(zhí)行一些邏輯。有時(shí)候指令的鉤子不止一個(gè),但是又是重復(fù)的邏輯操作時(shí),重復(fù)寫(xiě)一遍代碼顯然有點(diǎn)不夠優(yōu)雅。在Vue中,如果我們?cè)谧远x指令時(shí),需要在mountedupdated中實(shí)現(xiàn)相同的行為,并且不關(guān)心其他鉤子的情況,那么我們開(kāi)可以采用簡(jiǎn)寫(xiě):

app.directive('color', (el, binding) => {
    // 這將會(huì)在mounted和updated時(shí)調(diào)用
    el.style.color = binding.value;
})

對(duì)象字面量

我們之前的例子中,傳遞給指令的值只有一個(gè),如果我們想給指令傳入多個(gè)值應(yīng)該怎么操作呢?很簡(jiǎn)單,傳入一個(gè)字面量對(duì)象即可,可以直接在模板中聲明,也可以使用響應(yīng)式對(duì)象,在使用時(shí)binding.value就是一個(gè)對(duì)象了,而不是一個(gè)普通的值。

<script setup>
import {ref, reactive} from 'vue'
let hello = ref('')
const obj = reactive({
  hello: '',
  world: ''
})
</script>
<template>
  <input v-focus v-model="obj.hello" />
  <div v-reserve:foo.bar="obj"></div>
  <!-- <div v-reserve:foo.bar="{hello: obj.hello, world: obj.world}"></div> -->
</template>

對(duì)應(yīng)的,我們的指令也要小小的修改一下:

el.innerText = binding.value ? binding.value.hello.split('').reverse().join('') : '';

實(shí)現(xiàn)的效果還是和上面的保持一致。

在組件上使用指令

在元素上直接使用指令,我們可以在指令中操作DOM,這個(gè)已經(jīng)沒(méi)有問(wèn)題了。那如果在組件上使用指令會(huì)怎樣呢?組件其實(shí)就是把一些DOM元素封裝起來(lái),Vue3和Vue2不同,Vue3的模板中可以不止一個(gè)根節(jié)點(diǎn)。

我們新建一個(gè)Reverse.vue,以便后續(xù)作為組件引入。

Vue2:模板中只能有一個(gè)根節(jié)點(diǎn),因此會(huì)報(bào)錯(cuò)

// Reverse.vue
<template>
    <div></div>
    <div></div>
</template>

Vue3:模板中可以不止一個(gè)根節(jié)點(diǎn),正常

// Reverse.vue
<template>
    <div></div>
    <div></div>
</template>

既然指令是為了操作DOM元素,如果只有單個(gè)根節(jié)點(diǎn)那不會(huì)有問(wèn)題,例如:

<script setup>
...
import ReverseVue from './Reserve.vue'
...
</script>
<template>
  ...
  <ReverseVue v-reserve="obj"/>
</template>
// Reverse.vue
<template>
    <!-- v-reserve 指令會(huì)被應(yīng)用在此處 -->
    <div></div>
</template>

如果模板中是多個(gè)根節(jié)點(diǎn),就會(huì)拋出警告,并且不執(zhí)行指令

// Reverse.vue
<template>
    <!-- v-reserve 不會(huì)作用,并且會(huì)拋出警告 -->
    <div></div>
    <div></div>
</template>

結(jié)論:盡量不要在組件上使用自定義指令,除非能確定只會(huì)有一個(gè)根節(jié)點(diǎn)

幾個(gè)實(shí)用的自定義指令

以下舉例的指令都是全局指令

自動(dòng)聚焦v-focus

聚焦比較特殊,兄弟元素間只會(huì)有一個(gè)聚焦,即將該指令作用于兩個(gè)兄弟輸入框上,只會(huì)自動(dòng)聚焦一個(gè)

app.directive('focus', (el) => {
    el.focus();
})

防抖v-debounce

在實(shí)際項(xiàng)目開(kāi)發(fā)中,經(jīng)常會(huì)聽(tīng)到服務(wù)端的同事抱怨:前端怎么不做限流呀。前端做”限流“一般會(huì)采用防抖和節(jié)流,我們先來(lái)看如何實(shí)現(xiàn)防抖。

步驟:

  • 首先我們得知道怎么寫(xiě)一個(gè)防抖函數(shù)
  • 然后需要將防抖函數(shù)與el節(jié)點(diǎn)綁定,為了通用的話,還需要考慮傳入事件類型
  • 最后是卸載定時(shí)器等操作
app.directive('debounce', {
  mounted(el, binding) {
    // 至少需要回調(diào)函數(shù)以及監(jiān)聽(tīng)事件類型
    if (typeof binding.value.fn !== 'function' || !binding.value.event) return;
    let delay = 200; // 默認(rèn)延遲時(shí)間
    el.timer = null;
    el.handler = function() {
      if (el.timer) {
        clearTimeout(el.timer);
        el.timer = null;
      };
      el.timer = setTimeout(() => {
        binding.value.fn.apply(this, arguments)
        el.timer = null;
      }, binding.value.delay || delay);
    }
    el.addEventListener(binding.value.event, el.handler)
  },
  // 元素卸載前也記得清理定時(shí)器并且移除監(jiān)聽(tīng)事件
  beforeMount(el, binding) {
    if (el.timer) {
      clearTimeout(el.timer);
      el.timer = null;
    }
    el.removeEventListener(binding.value.event, el.handler)
  }
})

在模板中使用:

<script setup>
const handleClick = () => {
  console.log('防抖點(diǎn)擊');
}
</script>
<template>
  <button v-debounce="{fn: handleClick, event: 'click', delay: 200}">點(diǎn)擊試試</button>
</template>

運(yùn)行結(jié)果:

快速點(diǎn)擊按鈕并不會(huì)立即觸發(fā)handleClick,而是會(huì)在指定的延遲時(shí)間后才會(huì)觸發(fā)。

節(jié)流v-throttle

節(jié)流和防抖類似,都是用于前端”限流“。不同的是,防抖是限制執(zhí)行次數(shù),多次密集的觸發(fā)只會(huì)執(zhí)行最后一次,無(wú)規(guī)律,更關(guān)注結(jié)果;節(jié)流是限制執(zhí)行頻率,有節(jié)奏的執(zhí)行,有規(guī)律, 更關(guān)注過(guò)程。

節(jié)流的實(shí)現(xiàn)和防抖差不多:

app.directive('throttle', {
  mounted(el, binding) {
    // 至少需要回調(diào)函數(shù)以及監(jiān)聽(tīng)事件類型
    if (typeof binding.value.fn !== 'function' || !binding.value.event) return;
    let delay = 200;
    el.timer = null;
    el.handler = function() {
      if (el.timer) return;
      el.timer = setTimeout(() => {
        binding.value.fn.apply(this, arguments)
        el.timer = null;
      }, binding.value.delay || delay);
    }
    el.addEventListener(binding.value.event, el.handler)
  },
  // 元素卸載前也記得清理定時(shí)器并且移除監(jiān)聽(tīng)事件
  beforeMount(el, binding) {
    if (el.timer) {
      clearTimeout(el.timer);
      el.timer = null;
    }
    el.removeEventListener(binding.value.event, el.handler)
  }
})

在模板中使用:

<script setup>
import {reactive} from 'vue'
const obj = reactive({
  hello: '',
  world: ''
})
const handleInput = () => {
  console.log('節(jié)流輸入框的值:', obj.hello);
}
</script>
<template>
  <input v-throttle="{fn: handleInput, event: 'input', delay: 1000}" v-model="obj.hello" />
</template>

運(yùn)行結(jié)果:

handleInput并不會(huì)因?yàn)槲以谳斎肟蜉斎霑r(shí)的快慢而觸發(fā),而是在固定的時(shí)間間隔內(nèi)觸發(fā)一次,這就是節(jié)流。

彈窗隱藏v-hide

在實(shí)際開(kāi)發(fā)時(shí)會(huì)有這樣的需求:點(diǎn)擊某一個(gè)按鈕出現(xiàn)一個(gè)彈窗,然后點(diǎn)彈窗的其他區(qū)域時(shí)需要關(guān)閉彈窗,如果是點(diǎn)擊的彈窗本身,除非是關(guān)閉操作,否則不關(guān)閉彈窗。

想要實(shí)現(xiàn)這種效果,大多數(shù)人都會(huì)想到全局監(jiān)聽(tīng)click事件,并且判斷點(diǎn)擊的目標(biāo)元素和我們的彈窗元素是不是同一個(gè),如果不是那就隱藏彈窗。那么我們就來(lái)看看具體應(yīng)該怎么實(shí)現(xiàn):

app.directive('hide', {
  mounted(el, binding) {
    el.handler = function(e) {
      // 如果點(diǎn)擊范圍在綁定的元素范圍內(nèi),那么將不執(zhí)行指令操作,而是執(zhí)行原點(diǎn)擊事件
      if (el.contains(e.target)) return;
      if (typeof binding.value.fn === 'function') {
        // 綁定給指令的如果是一個(gè)函數(shù),那么將回調(diào)并指定this
        binding.value.fn.apply(this, arguments)
        // 并不推薦使用style的方式來(lái)隱藏元素,這樣的話控制彈窗的變量就無(wú)法改變,所以推薦使用回調(diào)函數(shù)
        // el.style.display = 'none';
        // 解除事件綁定
        document.removeEventListener('click', el.handler)
      }
    }
    // 監(jiān)聽(tīng)全局的點(diǎn)擊事件
    document.addEventListener('click', el.handler)
    // 如果同步綁定全局事件不生效,可以采用異步的方式
    // setTimeout(() => {
    //   document.addEventListener('click', el.handler)
    // }, 0);
  },
  // 解除事件綁定
  beforeMount(el) {
    document.removeEventListener('click', el.handler)
  }
})

在模板中使用:

<script setup>
import {ref} from 'vue'
let isShowModal = ref(false)
const showModal = () => {
  isShowModal.value = true;
}
const cancleModal = () => {
  console.log('cancleModal');
  isShowModal.value = false;
}
</script>
<template>
  <button @click.stop="showModal">點(diǎn)擊顯示彈窗</button>
  <div class="modal" v-hide="{fn: cancleModal}" v-if="isShowModal">
    <p>我是彈窗</p>
    <button @click.stop="cancleModal">關(guān)閉</button>
  </div>
</template>

運(yùn)行結(jié)果:

總結(jié)

本文盡量采用通俗易懂的方式,完整的梳理了如何在Vue3中自定義指令。合理的使用指令,可以更快的幫助我們解決問(wèn)題,值得注意的是:

  • 選擇指令的鉤子函數(shù)時(shí)需要明確,不同的鉤子函數(shù)呈現(xiàn)的效果是不一樣的
  • 應(yīng)該及時(shí)卸載鉤子函數(shù)定義的全局變量、定時(shí)器、事件綁定等,避免影響其他組件使用,以及內(nèi)存泄漏
  • 如果是涉及DOM操作的,我們第一時(shí)間應(yīng)該想到是不是可以抽離成指令的方式

到此這篇關(guān)于如何在Vue3中實(shí)現(xiàn)自定義指令的文章就介紹到這了,更多相關(guān)Vue3自定義指令內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Vue-cli Eslint在vscode里代碼自動(dòng)格式化的方法

    Vue-cli Eslint在vscode里代碼自動(dòng)格式化的方法

    本篇文章主要介紹了Vue-cli Eslint在vscode里代碼自動(dòng)格式化的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2018-02-02
  • vuepress打包部署踩坑及解決

    vuepress打包部署踩坑及解決

    這篇文章主要介紹了vuepress打包部署踩坑及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • vue實(shí)現(xiàn)登錄時(shí)滑塊驗(yàn)證

    vue實(shí)現(xiàn)登錄時(shí)滑塊驗(yàn)證

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)登錄時(shí)滑塊驗(yàn)證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-03-03
  • vuex-persist 使用場(chǎng)景分析

    vuex-persist 使用場(chǎng)景分析

    Vuex-Persist 是一個(gè)用于在 Vuex 中實(shí)現(xiàn)持久化狀態(tài)的庫(kù),它可以用來(lái)解決應(yīng)用程序在刷新瀏覽器或關(guān)閉頁(yè)面后丟失 Vuex store 中狀態(tài)的問(wèn)題,本文給大家介紹vuex-persist 可以用來(lái)干什么,感興趣的朋友一起看看吧
    2023-11-11
  • vue前端項(xiàng)目打包成Docker鏡像并運(yùn)行的實(shí)現(xiàn)

    vue前端項(xiàng)目打包成Docker鏡像并運(yùn)行的實(shí)現(xiàn)

    這篇文章主要介紹了vue前端項(xiàng)目打包成Docker鏡像并運(yùn)行的實(shí)現(xiàn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-08-08
  • 詳解vue-cli中配置sass

    詳解vue-cli中配置sass

    本篇文章主要介紹了詳解vue-cli中配置sass ,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧
    2017-06-06
  • vue解決Not?allowed?to?load?local?resource問(wèn)題的全過(guò)程

    vue解決Not?allowed?to?load?local?resource問(wèn)題的全過(guò)程

    這篇文章主要給大家介紹了關(guān)于vue解決Not?allowed?to?load?local?resource問(wèn)題的相關(guān)資料,文中通過(guò)圖文介紹的非常詳細(xì),需要的朋友可以參考下
    2022-10-10
  • 詳解element上傳組件before-remove鉤子問(wèn)題解決

    詳解element上傳組件before-remove鉤子問(wèn)題解決

    這篇文章主要介紹了詳解element上傳組件before-remove鉤子問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • vue elementUI 表單校驗(yàn)的實(shí)現(xiàn)代碼(多層嵌套)

    vue elementUI 表單校驗(yàn)的實(shí)現(xiàn)代碼(多層嵌套)

    這篇文章主要介紹了vue elementUI 表單校驗(yàn)的實(shí)現(xiàn)代碼(多層嵌套),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • vue中使用element ui的彈窗與echarts之間的問(wèn)題詳解

    vue中使用element ui的彈窗與echarts之間的問(wèn)題詳解

    這篇文章主要介紹了vue中使用element ui的彈窗與echarts之間的問(wèn)題詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-10-10

最新評(píng)論