目录
(1)inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
(2)bind:只调用一次,指令第一次绑定到元素上时调用,可以进行一些初始化的操作。
注意:bind 钩子函数在 inserted 钩子函数之前调用。
(4)componentUpdated:指令所在组件的 VNode 和子 VNode 全部更新后调用。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 函数中使用。
注意:除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
1.创建一个盒子位置固定的指令:src/directives/position.js
2.创建自定义指令统一管理文件:src/directives/index.js
4.调用全局自定义指令:src/views/Directive.vue
一.什么是自定义指令
Vue除了核心功能内置的指令如 v-model,v-if 之外,还允许我们自己定义指令。通过这些指令可以对 dom 元素进行底层操作。
二.定义指令
1.全局自定义指令
通过 directive 方法去定义全局指令。
inserted 函数是指令对象可以定义的几个钩子函数之一,表示被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
钩子函数有很多参数,el 是其中之一,表示指令绑定的元素,可以用来进行一些 DOM 操作。
// main.js
// 注册一个全局自定义指令 `v-focus`
Vue.directive("focus", {
// 当被绑定的元素插入到 DOM 中时触发
inserted(el) {
// 聚焦元素
el.focus();
},
});
2.组件局部自定义指令
export default {
directives: {
focus: {
// 指令的定义
inserted(el) {
el.focus();
},
},
},
};
3.指令使用
在需要操作的 dom 元素上绑定自己定义的指令。
<div class="directive">
<input style="width: 200px" v-focus />
</div>
此时页面刚渲染以后,input 标签就会获取焦点。
如果两个 input 都绑定 v-focus 指令,那么第二个 input 会获取标签。
<template>
<div class="directive">
<input style="width: 200px" v-focus /><br>
<input style="width: 200px" v-focus />
</div>
</template>
4.绑定值的自定义指令
钩子函数还有另一个参数 binding,是一个对象,其中的 value 属性就是指令的绑定值。
<template>
<!-- 绑定值的自定义指令 -->
<div class="directive">
<div v-test="name"></div>
</div>
</template>
<script>
export default {
data() {
return {
name: "我是名字",
};
},
directives: {
test: {
inserted(el, binding) {
el.innerHTML = binding.value;
},
},
},
};
</script>
三.自定义指令钩子函数和参数
前文提到了 inserted 函数和 el、binding参数,都只是自定义指令对象中的一部分。
1.钩子函数
(1)inserted:被绑定元素插入父节点时调用 (仅保证父节点存在,但不一定已被插入文档中)。
(2)bind:只调用一次,指令第一次绑定到元素上时调用,可以进行一些初始化的操作。
注意:bind 钩子函数在 inserted 钩子函数之前调用。
(3)update:所在组件的 VNode 更新时调用,但是可能发生在其子 VNode 更新之前。指令的值可能发生了改变,也可能没有。但是可以通过比较更新前后的值来忽略不必要的模板更新(惺惜的钩子函数在下)。
(4)componentUpdated:指令所在组件的 VNode 和子 VNode 全部更新后调用。
(5)unbind:只调用一次,在指令和元素解绑时调用。
2.钩子函数参数
el:绑定的元素,可用来进行一些 DOM 操作。
binding:一个对象:包含以下属性:
- name:指令名:不包含 v- 前缀。
- value:指令绑定的值,例如:v-test
="1 + 1"
中,绑定值为2。
- oldValue:指令绑定的前一个值,仅在 update 和 componentUpdated 函数中使用,无论值是否改变都可用。
- expression:字符串形式的指令表达式,例如 v-test
="1 + 1"
中,表达式为"1 + 1"。
- arg:传给指令的参数,例如 v-test:foo 中参数为 foo。
- modifiers:一个包含修饰符的对象,例如 v-test.foo.bar 中,修饰符对象为 {foo:true,bar:true} 。
vnode:Vue 编译生成的虚拟节点。
oldVnode:上一个虚拟节点,仅在 update 和 componentUpdated 函数中使用。
注意:除了 el 之外,其它参数都应该是只读的,切勿进行修改。如果需要在钩子之间共享数据,建议通过元素的 dataset 来进行。
3.动态指令参数
指令的参数可以动态,例如 v-test:[argument] = "value" 中,argument 参数可以根据组件实例数据更新。
比如创建一个自定义指令,用于固定布局将元素固定在页面上,可以创建一个通过指令来更新元素位置的自定义指令:
<template>
<div id="baseexample">
<p>Scroll down the page</p>
<p v-pin="200">Stick me 200px from the top of the page</p>
</div>
</template>
<script>
export default {
data() {
return {
};
},
directives: {
pin: {
bind(el, binding, vnode) {
el.style.position = "fixed";
el.style.top = binding.value + "px";
},
},
},
};
</script>
这会把该元素固定在距离页面顶部 200 像素的位置。但如果场景是我们需要把元素固定在左侧而不是顶部又该怎么办呢?这时使用动态参数就可以非常方便地根据每个组件实例来进行更新。
<template>
<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>
</template>
<script>
export default {
data() {
return {
direction: "left",
};
},
directives: {
pin: {
bind(el, binding, vnode) {
el.style.position = "fixed";
var s = binding.arg == "left" ? "left" : "top";
el.style[s] = binding.value + "px";
},
},
},
};
</script>
4.函数简写
当只有 bind 和 update 函数时,可以简写。
Vue.directive('color-swatch', function (el, binding) {
el.style.backgroundColor = binding.value
})
四.关于在 elementui 上使用自定义指令
比如在给 el-input 组件上添加自定义指令使其自动获取焦点。
<template>
<div class="directive">
<el-input style="width: 200px" v-focus />
</div>
</template>
<script>
export default {
directives: {
focus: {
// 指令的定义
inserted(el) {
el.focus();
},
},
},
};
</script>
但是实际上是没有效果的。
那为什么绑定在 input 标签就生效,但是绑定在 el-input 标签不生效呢?这是因为 el-input 的结构问题。
el-input 的 DOM 结构是下面这样的。
<div class="el-input">
<input type="text" autocomplete="off" placeholder="请输入内容" class="el-input__inner">
</div>
所以我们绑定 el-input 标签的时候,其实是绑定在 div 上的而不是 input 上的,这个时候 el.focus() 就没有用了。
所以为了让指令兼容 el-input,应该做一些修改。
directives: {
focus: {
// 指令的定义
inserted(el) {
if (el.tagName.toLocaleUpperCase() !== "INPUT") {
el = el.getElementsByTagName("input")[0];
}
el.focus();
},
},
},
五.vue3的自定义指令
只是做了钩子函数和参数的部分修改 。
1.钩子函数
directives: {
focus: {
// 在绑定元素的 attribute 前
// 或事件监听器应用前调用
created(el, binding, vnode) {
// 下面会介绍各个参数的细节
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {},
},
},
2.参数
el
:指令绑定到的元素。这可以用于直接操作 DOM。
binding
:一个对象,包含以下属性。
value
:传递给指令的值。例如在v-my-directive="1 + 1"
中,值是2
。oldValue
:之前的值,仅在beforeUpdate
和updated
中可用。无论值是否更改,它都可用。arg
:传递给指令的参数 (如果有的话)。例如在v-my-directive:foo
中,参数是"foo"
。modifiers
:一个包含修饰符的对象 (如果有的话)。例如在v-my-directive.foo.bar
中,修饰符对象是{ foo: true, bar: true }
。instance
:使用该指令的组件实例。dir
:指令的定义对象。
vnode:代表绑定元素的底层 VNode。
prevVnode
:代表之前的渲染中指令所绑定元素的 VNode。仅在 beforeUpdate
和 updated
钩子中可用。
3.函数简写
当只有mounted
和 updated
上实现相同的行为,除此之外并不需要其他钩子时可以简写。
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
六.vue3封装指令库
1.创建一个盒子位置固定的指令:src/directives/position.js
const pos = {
mounted(el, binding) {
// 传递给指令的值:盒子是否要固定
let pinned = binding.value
// 传递给指令的修饰符:表示盒子定在哪里
let position = binding.modifiers
// 传递给指令的参数:可以表示盒子的特性
let args = binding.arg
if (pinned) {
el.style.position = 'fixed'
if (args == 'warning') {
el.style.backgroundColor = 'pink'
} else {
el.style.backgroundColor = 'gray'
}
for (let val in position) {
if (position[val]) {
el.style[val] = '10px'
}
}
} else {
el.style.position = 'static'
el.style.backgroundColor = ''
}
},
}
export default pos
2.创建自定义指令统一管理文件:src/directives/index.js
import pos from './position'
const directives = {
pos,
}
export default {
install(app) {
Object.keys(directives).forEach((key) => {
app.directive(key, directives[key])
})
},
}
3.批量注册自定义指令:src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import Directives from './directives/index.js'
const app = createApp(App)
app.use(Directives)
app.use(router).mount('#app')
4.调用全局自定义指令:src/views/Directive.vue
<template>
<div class="directive">
<div class="box" v-pos:warning.right.top="true">{{msg}}</div>
<div class="box" v-pos:normal.right.bottom="true">{{msg}}</div>
<div class="box" v-pos:normal.left.bottom="true">{{msg}}</div>
<div class="box">{{msg}}</div>
</div>
</template>
<script>
import { defineComponent, ref } from 'vue'
export default defineComponent({
name: 'Directive',
setup() {
const msg = ref('message')
return {
msg
}
},
})
</script>
<style scoped>
.box {
height: 100px;
width: 100px;
text-align: center;
line-height: 100px;
color: aliceblue;
background-color: green;
}
</style>