前言
vue在除了提供默认的十几个内置的指令外,还允许开发人员根据实际情况自定义指令,那我们在何时使用它呢? 在vue项目中,一般情况下都可以操作dom来修改试图,但偶尔避免不了操作原生dom,当需要操作原生dom的时候,就可以用到自定义指令啦。
学习背景
页面上就两个元素:1号和2号,需求是点击2号,让1号显示;点击1号,1号也显示;点击1号和2号之外的区域,1号隐藏。所以“点击1号和2号之外的区域,1号隐藏”,这个自定义指令就派上用场了。 仔细想一下,element的select下拉框,点击下拉框以外的内容,下拉框消失,el-date也是。
实现思想
1.监听点击事件
2.判断点击事件是否为当前dom
3.取消监听
代码实现
<template>
<div class="whole">
<div v-show="state.showOne" ref="one" class="one" @click.stop="clickOne">这是第一个元素</div>
<div v-show="state.showTwo" ref="two" class="two" @click.stop="clickTwo">这是第二个元素</div>
</div>
</template>
<script setup>
import { reactive, getCurrentInstance } from 'vue';
const { proxy } = getCurrentInstance();
const state = reactive({
showOne: true,
showTwo: true
});
const clickOne = () => {
state.showOne = true;
};
const clickTwo = () => {
state.showOne = true;
};
document.addEventListener('click', e => {
if (proxy.$refs.one || proxy.$refs.two) {
let isSelf = proxy.$refs.one.contains(e.target) || proxy.$refs.two.contains(e.target);
if (!isSelf) {
state.showOne = false;
}
}
});
</script>
<style lang="less" scoped>
.whole {
display: flex;
align-items: center;
justify-content: space-around;
margin-top: 20px;
text-align: center;
.one {
width: 150px;
height: 150px;
background: #ff4eff;
border-radius: 10px;
cursor: pointer;
}
.two {
width: 150px;
height: 150px;
background: #3c3fdd;
border-radius: 10px;
cursor: pointer;
}
}
</style>
这样确实可以实现,如果只是一个地方还好,直接就给 document 增加一个 click 事件就完事,如果多个地方都用到那就比较麻烦,这时封装成一个指令就很便捷了。
vue3的写法:
app.directive('clickoutside', {
beforeMount(el, binding) {
document.addEventListener('click', (e) => {
el.contains(e.target) && binding.value();
}, false)
},
unmounted() {
document.removeEventListener('click', () => {}) // 不要忘记移除
}
})
具体使用:
<template>
<button v-clickoutside="clickoutside">点击</button>
</template>
<script setup>
import { defineComponent } from "vue";
export default defineComponent({
setup() {
return {
clickoutside(e) {
console.log('非自身点击触发')
}
};
},
});
</script>
vue2的实现过程:
- 实现clickoutside .js
const clickOutside = {
bind(el, binding) {
const documentHandler = (e) => {
if (el.contains(e.target)) return false;
if (binding.expression){
binding.value(e);
}
};
el.documentHandler = documentHandler;
document.addEventListener('click', documentHandler, false);
},
unbind(el){
document.removeEventListener('click', el.documentHandler, false);
delete el.documentHandler;
}
};
export default clickOutside;
- 实现vue中自定义组件的目录index.js
import clickOutside from "../clickOutside";
const directives = {
clickOutside
};
export default{
install(Vue){
Object.keys(directives).forEach((key) => {
Vue.directive(key, directives[key]);
});
}
};
- main.js中统一注册自定义指令
import Vue from "vue";
import App from "./App.vue";
import directives from "./components/index";
Vue.use(directives);
new Vue({
router,
store,
render: h => h(App)
}).$mount("#app");
- 页面使用
<template>
<div class="title" v-show="isShow" v-clickOutside="clickOutside">最新课程</div>
<span v-show="isOne">11111111111</span>
</template>
<script>
export default {
data() {
return {
isOne: false,
isShow: true
}
},
methods: {
clickOutside(){
this.isShow = false;
this.isOne = true;
}
}
</script>
欧克,至此v-clickOutside的指令就完成啦!撒花
自定义指令很方便,而在 Vue2 中给一个指令定义对象可以提供 bind、inserted、update、componentUpdated、unbind 五个钩子函数。详情
在新发布的 Vue3 中对指令也做了一些改造,主要就是对其中的钩子函数进行了优化升级,更多的自定义指令可以参考下面这篇文章。