# 自定义指令与插件
Vue 自定义指令
认识自定义指令
- 在 Vue 的模板语法中我们学习过各种各样的指令:v-show、v-for、v-model 等等,除了使用这些指令之外,Vue 也允许我们来自定义自己的指令。
- 注意:在 Vue 中,代码的复用和抽象主要还是通过组件;
- 通常在某些情况下,你需要对 DOM 元素进行底层操作,这个时候就会用到自定义指令;
- 自定义指令分为两种:
- 自定义局部指令:组件中通过 directives 选项,只能在当前组件中使用;
- 自定义全局指令:app 的 directive 方法,可以在任意组件中被使用;
- 比如我们来做一个非常简单的案例:当某个元素挂载完成后可以自定获取焦点
- 实现方式一:如果我们使用默认的实现方式,定义 ref 绑定到 input 中 , 调用 focus 方法;
- 实现方式二:自定义一个 v-focus 的局部指令;
- 实现方式三:自定义一个 v-focus 的全局指令;
默认方式实现聚焦
App.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| <template> <div class="app"> <input type="text" ref="inputRef" /> </div> </template>
<script setup> import { onMounted, ref } from 'vue'
const inputRef = ref(null)
onMounted(() => { inputRef.value.focus() }) </script>
|
Hooks 方式实现聚焦
./src/hooks/useInput.js
1 2 3 4 5 6 7 8 9
| import { ref, onMounted } from 'vue'
export default function useInput() { const inputRef = ref() onMounted(() => { inputRef.value?.focus() }) return { inputRef } }
|
App.vue
1 2 3 4 5 6 7 8 9 10
| <template> <div class="app"> <input type="text" ref="inputRef" /> </div> </template>
<script setup> import useInput from '@/hooks/usrInput.js' const { inputRef } = useInput() </script>
|
局部自定义指令实现
- 实现方式二:自定义一个 v-focus 的局部指令
- 这个自定义指令实现非常简单,我们只需要在组件选项中使用 directives 即可;
- 它是一个对象,在对象中编写我们自定义指令的名称(注意:这里不需要加 v-);
- 自定义指令有一个生命周期,是在组件挂载后调用的 mounted,我们可以在其中完成操作;
- Vue2 写法实现局部自定义指令 :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <template> <div class="app"> <input type="text" v-focus /> </div> </template>
<script> export default { directives: { focus: { //自定义指令的生命周期函数,指令应用的元素被挂载完毕时调用,el:指令应用的 DOM 节点 mounted(el) { el?.focus() console.log(el) }, }, }, } </script>
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| <template> <div class="app"> <input type="text" v-focus /> </div> </template>
<script setup> const vFocus = { //自定义指令的生命周期函数,指令应用的元素被挂载完毕时调用,el:指令应用的 DOM 节点 mounted(el) { el?.focus() }, } </script>
|
全局自定义指令实现
- 自定义一个全局的 v-focus 指令可以让我们在任何地方直接使用
main.js
1 2 3 4 5 6 7 8 9 10 11 12 13
| import { createApp } from 'vue' import App from './App.vue'
const app = createApp(App)
app.directive('focus', { mounted(el) { el?.focus() }, })
app.mount('#app')
|
./src/App.vue , 中使用自定义指令
1 2 3 4 5
| <template> <div class="app"> <input type="text" v-focus /> </div> </template>
|
全局自定义指令抽离
01 新建 ./src/directives/focus.js
1 2 3 4 5 6 7
| export default function directiveFocus(app) { app.directive('focus', { mounted(el) { el?.focus() }, }) }
|
02 新建 ./src/directives/index.js
1 2 3 4 5
| import directiveFocus from './focus.js'
export default function useDirectives(app) { directiveFocus(app) }
|
03 在 main.js 中导入并注册全局自定义指令
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue' import App from './App.vue' import useDirectives from './directives'
const app = createApp(App)
useDirectives(app)
app.mount('#app')
|
指令的生命周期
- 一个指令定义的对象,Vue 提供了如下的几个钩子函数:
created
:在绑定元素的 attribute 或事件监听器被应用之前调用;
beforeMount
:当指令第一次绑定到元素并且在挂载父组件之前调用;
mounted
:在绑定元素的父组件被挂载后调用;
beforeUpdate
:在更新包含组件的 VNode 之前调用;
updated
:在包含组件的 VNode 及其子组件的 VNode 更新后调用;
beforeUnmount
:在卸载绑定元素的父组件之前调用;
unmounted
:当指令与元素解除绑定且父组件已卸载时,只调用一次;
指令修饰符
- 如果我们指令需要接受一些参数或者修饰符应该如何操作呢?
- info 是参数的名称;
- aaa-bbb 是修饰符的名称;
- 后面是传入的具体的值;

- 在我们的生命周期中,我们可以通过 bindings 获取到对应的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <template> <div class="app"> <h2 v-why:kobe.abc.cba="message">哈哈哈</h2> </div> </template>
<script setup> const message = '你好李银河'
const vWhy = { mounted(el, bindings) { console.log(bindings.value) // 给自定义指令传入的值 el.textContent = bindings.value }, } </script>
|
案例_价格格式化指令
01 新建 ./src/directive/unit.js
1 2 3 4 5 6 7 8 9 10 11 12
| export default function directiveUnit(app) { app.directive('unit', { mounted(el, bingings) { const defaultText = el.textContent let unit = bingings.value if (!unit) { unit = '¥' } el.textContent = unit + defaultText }, }) }
|
02 新建 ./src/directives/index.js
1 2 3 4 5
| import directiveUnit from './unit.js'
export default function useDirectives(app) { directiveUnit(app) }
|
03 在 main.js 中导入并注册全局自定义指令
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue' import App from './App.vue' import useDirectives from './directives'
const app = createApp(App)
useDirectives(app)
app.mount('#app')
|
04 自定义指令的使用
1 2 3 4 5
| <template> <div class="app"> <h2 v-unit>{{123}}</h2> </div> </template>
|
案例_时间格式化指令
01 安装插件 : npm install dayjs
02 新建 ./src/directive/ftime.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| import dayjs from 'dayjs'
export default function directiveFtime(app) { app.directive('ftime', { mounted(el, bindings) { let timestamp = el.textContent if (timestamp.length === 10) { timestamp = timestamp * 1000 }
timestamp = Number(timestamp)
let value = bindings.value if (!value) { value = 'YYYY-MM-DD HH:mm:ss' }
const formatTime = dayjs(timestamp).format(value) el.textContent = formatTime }, }) }
|
02 新建 ./src/directives/index.js
1 2 3 4 5
| import directiveFtime from './ftime.js'
export default function useDirectives(app) { directiveFtime(app) }
|
03 在 main.js 中导入并注册全局自定义指令
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue' import App from './App.vue' import useDirectives from './directives'
const app = createApp(App)
useDirectives(app)
app.mount('#app')
|
04 自定义指令的使用
1 2 3 4 5 6
| <template> <div class="app"> <h2 v-ftime>{{1623549563257}}</h2> <h2 v-ftime="'YYYY/MM/DD hh:mm:ss'">{{1623549563257}}</h2> </div> </template>
|
案例_图片懒加载指令
01 自定义指令
./src/directives/lazy.js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| export default { mounted(el) { console.log('自定义指令绑定完毕', el) const imgSrc = el.src el.src = ''
const observer = new IntersectionObserver((entries) => { const { isIntersecting } = entries[0] if (isIntersecting) { el.src = imgSrc observer.unobserve(el) } })
observer.observe(el) }, }
|
02 在 入口文件 main.js 中注册自定义指令
1 2 3 4 5 6 7
| import { createApp } from 'vue' import App from './App.vue' import lazy from './directives/lazy'
const app = createApp(App) app.directive('lazy', lazy) app.mount('#app')
|
03 使用自定义指令
- 在任意组件中,给需要懒加载的图片添加
v-lazy
指令即可
1 2 3
| <img v-lazy src="../assets/images/01.webp" alt="" /> <img v-lazy src="../assets/images/02.webp" alt="" /> <img v-lazy src="../assets/images/03.webp" alt="" />
|
Teleport 内置组件
认识 Teleport
- 在组件化开发中,我们封装一个组件 A,在另外一个组件 B 中使用:
- 那么组件 A 中 template 的元素,会被挂载到组件 B 中 template 的某个位置;
- 最终我们的应用程序会形成一棵 DOM 树结构;
- 但是某些情况下,我们希望组件不是挂载在这个组件树上的,可能是移动到 Vue app 之外的其他位置:
- 比如移动到 body 元素上,或者我们有其他的 div#app 之外的元素上;
- 这个时候我们就可以通过 teleport 来完成;
- Teleport 是什么呢?
- 它是一个 Vue 提供的内置组件,类似于 react 的 Portals ;
- teleport 翻译过来是心灵传输、远距离运输的意思;
- 它有两个属性:
- to:指定将其中的内容移动到的目标元素,可以使用选择器;
- disabled:是否禁用 teleport 的功能;
和组件结合使用
1 2 3 4 5 6 7 8
| <template> <div class="app"> <teleport to="body"> <HelloWorld></HelloWorld> </teleport> </div> </template>
|
- 如果我们将多个 teleport 应用到同一个目标上( to 的值相同),那么这些目标会进行合并:

Suspense 内置组件
- 注意:目前(2022-08-01)Suspense 显示的是一个实验性的特性,API 随时可能会修改。
- Suspense 是一个内置的全局组件,该组件有两个插槽:
- default:如果 default 可以显示,那么显示 default 的内容;
- fallback:如果 default 无法显示,那么会显示 fallback 插槽的内容;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <div class="app"> <suspense> <template #default> <HelloWorld></HelloWorld> </template> <template #fallback> <h2>Loading</h2> </template> </suspense> </div> </template>
<script setup> import { defineAsyncComponent } from 'vue' const HelloWorld = defineAsyncComponent(() => import('./components/HelloWorld.vue')) </script>
|
插件
认识插件
- 通常我们向 Vue 全局添加一些功能时,会采用插件的模式,它有两种编写方式:
- 对象类型:一个对象,但是必须包含一个 install 的函数,该函数会在安装插件时执行;
- 函数类型:一个 function,这个函数会在安装插件时自动执行;
- 插件可以完成的功能没有限制,比如下面的几种都是可以的:
- 添加全局方法或者 property,通过把它们添加到 config.globalProperties 上实现;
- 添加全局资源:指令 / 过滤器 / 过渡等;
- 通过全局 mixin 来添加一些组件选项;
- 一个库,提供自己的 API,同时提供上面提到的一个或多个功能;
插件的本质
对象写法
main.js
1 2 3 4 5 6
| app.use({ install: function (app) { console.log('传入对象的 install 被执行', app) }, })
|
函数写法
main.js
1 2 3 4
| app.use(function (app) { console.log('传入的函数被执行', app) })
|