1. 如何实现一个自定义指令,控制元素的权限(如按钮权限)?
问题解析
- 核心需求:根据用户权限动态显示/隐藏元素(如按钮)。
- 难点:权限逻辑复用性、响应式更新权限状态。
解决方案
Vue.directive('permission', {
inserted(el, binding, vnode) {
const { value: requiredPermission } = binding;
const userPermissions = vnode.context.$store.getters.userPermissions;
if (!requiredPermission || !userPermissions.includes(requiredPermission)) {
el.parentNode?.removeChild(el);
}
}
});
<button v-permission="'edit'">编辑</button>
优化点
- 响应式更新:在
update
钩子中处理权限变化,重新渲染。
- 服务端权限验证:可通过
binding.value
传递异步权限码。
2. 如何用指令实现全局防抖(v-debounce)?
问题解析
- 核心需求:防止按钮重复点击或输入框频繁触发事件。
- 难点:通用性(支持多种事件)、参数传递(防抖时间)。
解决方案
Vue.directive('debounce', {
inserted(el, binding) {
const { value: handler, arg: event = 'click', modifiers } = binding
const delay = modifiers.delay || 300
let timer = null
el.addEventListener(event, (...args) => {
clearTimeout(timer)
timer = setTimeout(() => handler.apply(this, args), delay)
})
}
})
// 使用:防抖点击事件,延迟500ms
<button v-debounce:click.delay="submitForm">提交</button>
优化点
- 支持修饰符:通过
modifiers
配置不同防抖时间。
- 内存泄漏处理:在
unbind
钩子中移除事件监听。
3. 如何实现一个拖拽指令(v-draggable)?
问题解析
- 核心需求:让元素可拖拽,支持边界限制。
- 难点:DOM 操作、事件解绑、性能优化。
解决方案
Vue.directive('draggable', {
inserted(el) {
let isDragging = false
let initialX = 0, initialY = 0
const onMouseDown = (e) => {
isDragging = true
initialX = e.clientX - el.offsetLeft
initialY = e.clientY - el.offsetTop
document.addEventListener('mousemove', onMouseMove)
document.addEventListener('mouseup', onMouseUp)
}
const onMouseMove = (e) => {
if (!isDragging) return
const x = e.clientX - initialX
const y = e.clientY - initialY
el.style.left = `${x}px`
el.style.top = `${y}px`
}
const onMouseUp = () => {
isDragging = false
document.removeEventListener('mousemove', onMouseMove)
document.removeEventListener('mouseup', onMouseUp)
}
el.addEventListener('mousedown', onMouseDown)
},
unbind(el) {
// 清理事件防止内存泄漏
el.removeEventListener('mousedown', onMouseDown)
}
})
// 使用
<div v-draggable style="position: absolute;">拖拽我</div>
优化点
- 边界限制:在
onMouseMove
中计算元素位置时添加边界判断。
- 性能优化:使用
transform
代替 left/top
减少回流。
4. 如何通过指令实现图片懒加载(v-lazy)?
问题解析
- 核心需求:图片进入视口时再加载资源。
- 难点:交叉观察器(IntersectionObserver)的使用、占位符设计。
解决方案
javascriptCopy Code
Vue.directive('lazy', {
inserted(el, binding) {
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = new Image()
img.src = binding.value
img.onload = () => el.src = binding.value
observer.unobserve(el)
}
})
})
observer.observe(el)
}
})
// 使用
<img v-lazy="'https://example.com/large-image.jpg'" src="placeholder.jpg">
优化点
- 兼容性:降级方案(如
scroll
事件监听)。
- 错误处理:添加
onerror
回调显示默认图片。
5. 如何设计一个支持动态内容的指令(如 Tooltip)?
问题解析
- 核心需求:鼠标悬停时显示动态内容提示。
- 难点:动态内容渲染、位置计算、组件化与指令的协作。
解决方案
Vue.directive('tooltip', {
bind(el, binding) {
const tooltip = document.createElement('div')
tooltip.className = 'custom-tooltip'
document.body.appendChild(tooltip)
el.addEventListener('mouseenter', () => {
tooltip.textContent = binding.value
const rect = el.getBoundingClientRect()
tooltip.style.left = `${rect.left + rect.width / 2}px`
tooltip.style.top = `${rect.top - 30}px`
tooltip.style.display = 'block'
})
el.addEventListener('mouseleave', () => {
tooltip.style.display = 'none'
})
},
unbind(el) {
// 清理 Tooltip 元素
const tooltip = document.querySelector('.custom-tooltip')
tooltip?.remove()
}
})
// 使用
<button v-tooltip="'这是提示内容'">悬停查看提示</button>
优化点
- 内容动态更新:在
update
钩子中更新 tooltip.textContent
。
- 动画效果:通过 CSS 过渡或第三方动画库增强交互。
复杂场景设计原则
- 解耦与复用:将指令逻辑拆分为独立函数,方便复用。
- 性能优化:避免在指令中频繁操作 DOM,优先使用 CSS 或
requestAnimationFrame
。
- 响应式处理:通过
binding.value
监听数据变化,更新指令行为。
- 内存管理:在
unbind
或 beforeUnmount
中清理事件和对象。