在vue中我们使用较多的v-if
、v-show
、v-module
这种以v- 开头的命令是vue自带的指令,通过vue这一套指令,我们可以更加友好的实现数据驱动视图 的操作。 当我们也有类似需求像将某个功能封装为自定义指令,让功能更加丰富和交互效果更加灵活,同时也能提高代码的可维护性和性能。比如在需要调用的地方如同v-if
写在对应标签元素上,就直接实现这个功能。vue也考虑到了这一点,所有提出自定义指令的概念,这篇文档记录我学习vue自定义指令的理解。
使用Vue的自定义指令有以下几个好处:
- 扩展HTML功能:自定义指令能够扩展HTML元素的功能,通过为元素添加自定义行为和样式,使得开发者可以更灵活地控制和定制页面的交互效果。
- 代码复用:通过封装一些常用的DOM操作或特定的UI交互逻辑为自定义指令,可以在不同的组件中复用这些指令,减少重复的代码编写。
- 提高可维护性:通过将具体的DOM操作封装为自定义指令,可以将这部分逻辑与组件的其它部分隔离,提高代码的可维护性和可读性。
- 增强交互性:自定义指令可以方便地实现一些特定的交互效果,例如拖拽、滚动加载、表单验证等。通过自定义指令,可以将这些交互行为与业务逻辑解耦,使得代码更加清晰和易于维护。
- 更好的性能:通过自定义指令,可以直接操作DOM元素,而无需通过Vue的响应式机制来更新视图。在某些情况下,使用自定义指令可以提高应用的性能。
明白自定义组件的好处和作用,在页面中该如何使用
指令的使用
注册指令
自定义指令可以通过Vue实例的directive
方法注册,注册指令有两种方式,一种是全局注册 、局部注册,面对不同场景下有不同选择,比如说注册一个点击和复制data值的组件采用全局注册最好,因为不止一个地方会调用。比如说,对一个页面按钮级别权限控制,肯定对那个页面组件有用,其他地方没啥用,采用局部注册最好。
全局注册
全局注册的自定义指令可以在整个应用程序中的任何组件中使用。通过Vue.directive
方法可以将一个自定义指令注册为全局指令。然后,你可以在任何组件的模板中使用这个自定义指令。
js
// 全局注册自定义指令
Vue.directive('my-directive', { bind: function(el, binding, vnode) {
// 指令绑定时的逻辑
},
// 其他钩子函数
});
局部注册
局部注册的自定义指令只能在某个组件内部使用。在组件的选项对象中,通过directives
属性 来定义和注册自定义指令。
js
// 局部注册自定义指令
export default {
directives: {
'my-directive': {
bind: function(el, binding, vnode) {
// 指令绑定时的逻辑
},
// 其他钩子函数
}
},
// 组件的其他选项
}
指令生命周期
定义一个自定义指令需要指定以下几个属性:
bind
:在指令第一次绑定到元素时调用,可以在这里进行一次性的设置。inserted
:被绑定的元素插入到父节点时调用。update
:被绑定元素的值更新时调用,可以根据新的值进行相应的操作。componentUpdated
:被绑定元素及其子元素的所有更新完成后调用。unbind
:指令与元素解绑时调用。
比如下例,在dircective声明一个自定义组件test 在页面中可以标签中写上v-test来调用指令。
html
<div v-test>点击test</div>
js
//局部注册
directives:{
test:{
bind:(el,binding)=>{
console.log("初始化");
console.log(el);
console.log(binding);
},
// 指令的定义
inserted:function(el,binding){
console.log("插入");
console.log(el);
console.log(binding);
},
update:function(el,binding){
console.log("更新");
console.log(el);
console.log(binding);
},
componentUpdated:function(el,binding){
console.log("更新完成");
console.log(el);
console.log(binding);
},
unbind:function(el,binding){
console.log("移除");
console.log(el);
console.log(binding);
}
},
},
使用场景
先放上我写的案例代码,下面将结合不同场景功能,在对应指令不同生命周期中如何使用。
html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
</style>
</head>
<body>
<div id="app">
<div v-test>
点击test
</div>
<!-- 点击复制功能 -->
<div v-copy="temp">
点击hello world
</div>
<!-- 点击更新内容 -->
<div v-event="temp">
点击hello world 更改为 你好
</div>
<!-- 权限控制 -->
<div>
<input type="radio" v-model="radio" value="true" checked/>
<input type="radio" v-model="radio" value="false"/>
</div>
<div >
权限控制
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
<script>
var app = new Vue({
el:'#app',
data:{
temp:"hallo world",
radio:true,
},
mounted(){
},
directives:{
test:{
bind:(el,binding)=>{
console.log("初始化");
console.log(el);
console.log(binding);
},
// 指令的定义
inserted:function(el,binding){
console.log("插入");
console.log(el);
console.log(binding);
},
update:function(el,binding){
console.log("更新");
console.log(el);
console.log(binding);
},
componentUpdated:function(el,binding){
console.log("更新完成");
console.log(el);
console.log(binding);
},
unbind:function(el,binding){
console.log("移除");
console.log(el);
console.log(binding);
}
},
},
mounted(){
console.log("初始化完成");
this.$on("longpressss",(val)=>{
console.log("抛出事件",val);
this.radio = val;
});
},
methods:{
}
});
</script>
</body>
</html>
点击复制
实现点击复制功能比较简单,注册了一个名为copy的指令。指令的bind钩子函数绑定了点击事件,并在点击时执行复制操作。通过binding.value
可以获取到指令绑定的值,即要复制的文本内容。
然后,在模板中使用v-copy
指令,将要复制的文本作为指令的值传入。当按钮被点击时,指令的bind钩子函数会被触发,从而执行复制操作。
需要注意的是,由于安全性限制,复制操作只能在用户主动触发的事件中执行,例如点击事件。而无法在页面加载或其他非用户交互事件中自动执行复制操作。
下面将简单用局部注册的方式进行演示:
js
directives:{
test:{...之前代码},
copy:{
bind:(el,binding)=>{
console.log("初始化");
//初始化给el添加点击事件
el.onclick = function(){
// 绑定事件
console.log("复制",binding,el.innerText);
var textarea = document.createElement("textarea");
textarea.value = binding.value;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
}
},
},
}
在html地方给div标签添加v-copy
,运行页面点击就可以看到复制data
元素下temp
的值hallo world
html
<!-- 点击复制功能 -->
<div v-copy="temp">
点击hello world
</div>
说明下 为啥要添加textarea,不添加textarea元素可以用吗?
需要添加textarea元素的原因是,document.execCommand('copy')方法要求在执行复制操作之前,必须先选中要复制的文本内容。
通过创建一个临时的textarea元素,并将要复制的文本内容设置为其值,然后将该元素添加到DOM树中,可以成功选中并复制文本内容。这是因为textarea元素具有可选中文本的特性。
所以,为了在Vue中实现复制文本的自定义指令,需要添加临时的textarea元素来实现复制操作。如果直接在el上调用select()方法,复制操作将无法正常工作。
为啥取binding.value 而不是el.innerText?
在自定义指令中,取binding.value而不是el.innerText的原因是为了提供更灵活的使用方式。
当使用指令时,我们可以将任意表达式作为指令的值传递。这样,我们可以动态地从组件实例中获取数据、计算属性等,并将其作为要复制的文本内容。这种方式允许我们根据具体需求来决定要复制的文本内容,并且可以在组件使用该指令时进行动态更新。
如果直接使用el.innerText,则意味着我们只能复制指令所绑定元素的当前文本内容。这样会限制了复制的灵活性,并且无法动态更新要复制的文本内容。
通过使用binding.value,我们可以从指令的值中获取到所需的文本内容,而不受限于绑定元素的当前文本内容。这使得指令更具通用性和可扩展性。
指令事件抛出
先注入自定义指令v-event
,在event指令中添加vnode
属性,通过vnode.context.settemp("你好");
抛出事件,指向调用的methods
函数settemp
,主要原因自定义指令本身不能直接修改组件实例中的数据。
Vue的自定义指令主要用于操作DOM元素,而不应该用于直接修改组件实例的数据。如果你需要修改组件实例中的数据,应该在指令中通过触发事件或调用方法来与组件通信,然后由组件来修改数据。 代码如下:
html
<!-- 点击更新内容 -->
<div v-event="temp">
点击hello world 更改为 你好
</div>
js
directives:{
//...之前代码
copy:{
bind:(el,binding)=>{//...之前代码}.
update:function(el,binding){
console.log("更新");
//初始化给el添加点击事件
el.onclick = function(){
// 绑定事件
console.log("复制",binding,el.innerText);
var textarea = document.createElement("textarea");
textarea.value = binding.value;
document.body.appendChild(textarea);
textarea.select();
document.execCommand('copy');
textarea.remove();
}
}
}
,event:{
bind:(el,binding,vnode)=>{
console.log("初始化");
//初始化给el添加点击事件
el.onclick = function(){
// 事件抛出
vnode.context.settemp("你好");
}
}
}
//....其他代码
methods:{
//函数设置指令传入值
settemp(str){
console.log("设置值",str);
this.temp = str;
}
}
在上面的代码中,copy
指令添加update
事件,主要测试 event
调用settemp(str)
方法后修改temp值,那么对应copy指令的点击事件也需要跟着修改。
按钮级权限控制
这里简单写案例,通过一个单选按钮控制文字是否显示,作为一个引子,可以深入探讨理解。
html
<!-- 权限控制 -->
<div>
<input type="radio" v-model="radio" value="true" />
<input type="radio" v-model="radio" value="false"/>
</div>
<div v-jurisdiction="radio">
权限控制
</div>
js
jurisdiction:{
bind:()=>{
},
update:(el,binding)=>{
if(binding.value == "true"){
el.style.display = "none";
console.log(binding.value,"权限控制");
}else{
el.style.display = "block";
}
},
}
当用户点击input
对应属性radio
发生改变,进而触发v-jurisdiction
指令的update
函数。 通过参数el
获取到当前指令绑定的dom元素,通过对DOM属性进行操作来实现一些功能。例如上例修改style.display
。
其他案例推荐,可以参考学习这篇博客分享8个非常实用的Vue自定义指令 - 掘金 (juejin.cn)