Vue3 自定义指令实战:从焦点管理到权限控制
背景与问题界定 Vue3 的自定义指令相比 Vue2 有重大的 API 变更——钩子函数从 bind/inserted/update 等 5 个调整为 created/mounted/updated/unmounted 等统一生命周期风格。这一变化让指令的行为更加可预测,但也带来了迁移成本。在实际项目中,自定义指令的编排能力经常被低估:许多可以在指令层面优雅解决的问题,开发者在组件内用 watch 和 onMounted 手动实现了。例如表单页面的自动聚焦需要遍历所有输入框 DOM;按钮级别的权限控制需要在每个模板片段中写 v-if="hasPermission('xxx')";滚动加载大量监听器散落在各个组件的 setup 中。这些问题可以通过精心设计的自定义指令系统化解决,同时减少模板中的逻辑噪声。 目标拆解与工程约束 指令的响应式参数绑定:指令值应支持响应式变量,当变量变化时指令行为自动更新,不需要手动更新 DOM。 资源生命周期管理:指令绑定的 DOM 事件监听器、IntersectionObserver、定时器等资源必须在指令 unmounted 时完全释放。 指令组合:多个指令作用于同一元素时不应相互冲突,例如 v-focus 和 v-tooltip 同时作用在一个输入框上应各自正常工作。 指令可测试:支持通过 @vue/test-utils 的 attachTo 挂载指令到真实 DOM,独立验证指令的 DOM 操作结果。 方案设计 我们将自定义指令分为三个层级:通用工具指令、业务增强指令 和 权限编排指令。 通用工具指令包括 v-focus 自动聚焦、v-debounce 防抖输入、v-click-outside 点击外部关闭等。这些指令只依赖标准的 DOM API,不引入任何业务逻辑,可以作为一个独立的 npm 包维护: // v-debounce 示例 const vDebounce: Directive = { mounted(el: HTMLInputElement, binding) { const delay = binding.value ?? 300 let timer: ReturnType<typeof setTimeout> | null = null el.addEventListener('input', (e: Event) => { if (timer) clearTimeout(timer) const target = e.target as HTMLInputElement timer = setTimeout(() => { el.dispatchEvent(new CustomEvent('debounce-update', { detail: { value: target.value } })) }, delay) }) }, unmounted() { // 全局注册时 Vue 自动处理,独立使用时需自行清理 } } 业务增强指令处理常见的业务模式。例如 v-ellipsis 实现可展开的多行文本截断,v-infinite-scroll 提供列表触底加载。这些指令将特定的业务交互逻辑封装到指令层,保持组件的模板干净。 ...