vue3学习记录-自定义指令
vue3学习记录-自定义指令
- 1.指令钩子
- 1.1 局部注册
- 1.2 全局注册
- 2.简化形式
- 3.对象字面量
- 4.在组件上使用
- 5.实例
- 5.1 权限控制指令
- 5.2 图片懒加载指令
- 5.3 点击外部关闭指令
- 5.4 复制文本指令
- 5.5 防抖指令
1.指令钩子
vue3的自定义指令
1.1 局部注册
<script setup>
import A from './components/A.vue';
import B from './components/B.vue';
import CustomForm from './components/CustomForm.vue';
import { ref } from 'vue';
const flag = ref(true);
const vShowtext = {
//必须以 vNameOfDirective 的形式来命名本地自定义指令,以使得它们可以直接在模板中使用。
created(el, binding, vnode) {
console.log('Created', el, binding, vnode)
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {
console.log('BeforeMount', el, binding, vnode)
},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {
console.log('Mounted', el, binding, vnode)
},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {
console.log('BeforeUpdate', el, binding, vnode, prevVnode)
},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {
console.log('Updated', el, binding, vnode, prevVnode)
},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {
console.log('BeforeUnmount', el, binding, vnode)
},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {
console.log('Unmounted', el, binding, vnode)
}
}
</script>
<template>
<div class="container">
<el-button @click="flag=!flag">change</el-button>
<div v-showtext:aaa.b.c="flag" class="box">自定义指令</div>
</div>
</template>
<style scoped>
.container {
width: 300px;
height: 200px;
margin: 0 auto;
margin-bottom: 8px;
.box{
background-color: aquamarine;
height: 100%;
}
}
</style>
1.2 全局注册
const app = createApp(App)
app.directive('demo',{
mounted(el,binding){
console.log('全局注册的组件',el,binding)
}
})
2.简化形式
对于自定义指令来说,一个很常见的情况是仅仅需要在 mounted 和 updated 上实现相同的行为,除此之外并不需要其他钩子。这种情况下我们可以直接用一个函数来定义指令,如下所示:
<div v-color="color"></div>
app.directive('color', (el, binding) => {
// 这会在 `mounted` 和 `updated` 时都调用
el.style.color = binding.value
})
3.对象字面量
如果你的指令需要多个值,你可以向它传递一个 JavaScript 对象字面量
<div v-showtext:aaa.b.c="{'background-color':'red',fontSize:'16px',flag}" class="box">自定义指令</div>
通过binding.value.属性名 可以访问到对应的属性值
4.在组件上使用
不推荐在组件上使用自定义指令。为什么呢。
1.组件生命周期与指令钩子的执行时机不一致
<script setup>
<script setup>
import A from './components/A.vue';
import B from './components/B.vue';
import CustomForm from './components/CustomForm.vue';
import { ref } from 'vue';
const flag = ref(true);
const vFocus = {
created(el, binding, vnode) {
console.log('Created', el, binding, vnode)
},
// 在元素被插入到 DOM 前调用
beforeMount(el, binding, vnode) {
console.log('BeforeMount', el, binding, vnode)
},
// 在绑定元素的父组件
// 及他自己的所有子节点都挂载完成后调用
mounted(el, binding, vnode) {
console.log('Mounted', el, binding.value, vnode)
el.style.color = binding.value['background-color']
el.focus();
},
// 绑定元素的父组件更新前调用
beforeUpdate(el, binding, vnode, prevVnode) {
console.log('BeforeUpdate', el, binding, vnode, prevVnode)
},
// 在绑定元素的父组件
// 及他自己的所有子节点都更新后调用
updated(el, binding, vnode, prevVnode) {
console.log('Updated', el, binding, vnode, prevVnode)
},
// 绑定元素的父组件卸载前调用
beforeUnmount(el, binding, vnode) {
console.log('BeforeUnmount', el, binding, vnode)
},
// 绑定元素的父组件卸载后调用
unmounted(el, binding, vnode) {
console.log('Unmounted', el, binding, vnode)
}
}
</script>
<template>
<div class="container">
<el-button @click="flag=!flag">change</el-button>
<A v-focus:aaa.b.c="{'background-color':'red',fontSize:'16px',flag}" class="box">自定义指令</A>
</div>
</template>
<style scoped>
.container {
width: 300px;
height: 200px;
margin: 0 auto;
margin-bottom: 8px;
.box{
background-color: aquamarine;
height: 100%;
}
}
</style>
//A组件
<script setup>
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
console.log('组件 mounted')
})
onUnmounted(() => {
console.log('组件 unmounted')
})
</script>
<template>
<div>
a组件
<header>头部</header>
<main>主要内容</main>
<input type="text">
</div>
</template>
可以看到是父组件的自定义指令钩子函数执行完了才会去执行子组件的生命周期
2.组件可能有多个根节点,导致指令行为不明确
可以根据我的自定义指令v-focus的意思就是聚焦嘛,但是可以看到自定义的mounted钩子拿到的el是整个子组件div的,不清楚指令应该作用于哪个根节点。虽然子组件的dom div都拿到了,用js也能把其中的input标签自动聚焦,但是完全没必要吧。还不如把自定义指令写在子组件的input上呢。
但凡子组件不是很特殊的,建议,最好不要在组件上使用自定义指令
5.实例
5.1 权限控制指令
app.directive('permission', (el, binding) => {
const { value } = binding
//const userRoles = getCurrentUserRoles() // 获取当前用户角色的方法
const userRoles = ['admin'] //模拟数据
if (value && value instanceof Array && value.length > 0) {
const hasPermission = userRoles.some(role => value.includes(role))
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el)
}
} else {
throw new Error(`需要指定权限,例如 v-permission="['admin','editor']"`)
}
}
)
<template>
<div class="container">
<button v-permission="['admin']">管理员按钮</button>
<button v-permission="['editor']">编辑者按钮</button>
</div>
</template>
结果:
5.2 图片懒加载指令
app.directive('lazyload', {
created(el, binding) {
el.setAttribute('data-src', binding.value)
},
mounted(el) {
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target
const dataSrc = img.getAttribute('data-src')
if (dataSrc) {
img.src = dataSrc
observer.unobserve(img)
}
}
})
})
observer.observe(el)
}
})
<template>
<div class="container">
<img v-lazyload="'https://picsum.photos/200/300'" class="lazy-image" alt="Lazy loaded image">
<img
v-for="id in 20"
:key="id"
v-lazyload="`https://picsum.photos/200/300?random=${id}`"
alt="随机图片"
class="lazy-image"
>
</div>
</template>
<style scoped>
.container {
display: grid;
gap: 20px;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
.lazy-image {
width: 100%;
height: 300px;
object-fit: cover;
background-color: #f5f5f5;
/* 加载前的背景色 */
transition: opacity 0.3s;
}
}
</style>
效果:
5.3 点击外部关闭指令
app.directive('clickOutside', {
mounted(el, binding) {
el._clickOutside = (event) => {
if (!(el === event.target || el.contains(event.target))) {
binding.value(event)
}
}
document.addEventListener('click', el._clickOutside)
},
unmounted(el) {
document.removeEventListener('click', el._clickOutside)
}
})
<template>
<div class="container">
<div v-click-outside="closeDropdown" class="dropdown">
拉菜单内容
</div>
</div>
</template>
<style scoped>
.container {
width: 300px;
height: 200px;
margin: 0 auto;
margin-bottom: 8px;
.dropdown {
height: 100px;
background-color: aqua;
}
}
</style>
5.4 复制文本指令
app.directive('copy', {
mounted(el, binding) {
el._copyHandler = () => {
const value = binding.value
const input = document.createElement('input')
input.value = value
document.body.appendChild(input)
input.select()
document.execCommand('copy')
document.body.removeChild(input)
// 可以添加复制成功的提示
alert('复制成功!')
}
el.addEventListener('click', el._copyHandler)
},
unmounted(el) {
el.removeEventListener('click', el._copyHandler)
}
})
<script setup>
const textToCopy = '要复制的文本';
</script>
<template>
<button v-copy="textToCopy">点击复制文本</button>
</template>
5.5 防抖指令
app.directive('debounce', {
mounted(el, binding) {
const { value, arg = 300 } = binding // arg 用于设置延迟时间
let timer = null
el.addEventListener('click', () => {
if (timer) {
clearTimeout(timer)
}
timer = setTimeout(() => {
value()
}, Number(arg))
})
}
})
<script setup>
const handleClick = () => {
console.log('按钮被点击')
}
</script>
<template>
<button v-debounce:500="handleClick">防抖按钮</button>
</template>