Vue 自定义指令是什么?和组件、组合式函数怎么区分?
简化版
自定义指令用于封装可复用的底层 DOM 行为,比如自动聚焦、权限控制、点击外部关闭、懒加载。组件封装 UI 和状态,组合式函数封装响应式逻辑,自定义指令更适合直接操作 DOM 的场景。
详细版
自定义指令示例:
const vFocus = {
mounted(el: HTMLInputElement) {
el.focus();
},
};
使用:
<input v-focus />
它的钩子和元素生命周期相关,例如 mounted、updated、unmounted。面试重点是:不要滥用指令处理业务状态;需要 UI 结构用组件,需要逻辑复用用 composable,需要 DOM 行为复用才考虑指令。
完整版教学
一、自定义指令解决可复用 DOM 行为
Vue 组件主要关心声明式渲染,但真实项目仍会遇到一些直接 DOM 行为。 例如输入框自动聚焦、滚动加载、点击外部关闭、图片懒加载、权限按钮隐藏。 这些行为如果散落在组件里,会重复且难维护。
<input v-focus />
<button v-permission="'admin'">删除</button>
自定义指令把这些 DOM 行为封装成模板上的指令。
调用方只看模板就知道这个元素有什么额外行为。
这就是它比在 mounted 里手写 DOM 操作更清晰的地方。
二、指令钩子围绕元素生命周期执行
Vue3 自定义指令常见钩子包括 created、beforeMount、mounted、beforeUpdate、updated、beforeUnmount、unmounted。
实际最常用的是 mounted 和 unmounted。
前者绑定 DOM 行为,后者清理事件监听。
const vClickOutside = {
mounted(el: HTMLElement, binding: any) {
const handler = (event: MouseEvent) => {
if (!el.contains(event.target as Node)) {
binding.value(event);
}
};
document.addEventListener('click', handler);
(el as any).__clickOutside__ = handler;
},
unmounted(el: HTMLElement) {
document.removeEventListener('click', (el as any).__clickOutside__);
},
};
如果页面有 10 个下拉框,这个指令能避免写 10 份 document 点击监听。
同时 unmounted 清理监听,能减少内存泄漏风险。
三、binding 提供指令参数和值
指令钩子的第二个参数是 binding。
它包含 value、oldValue、arg、modifiers 等信息。
这让一个指令可以根据参数和修饰符调整行为。
<button v-permission="'admin'">删除</button>
<img v-lazy:src.once="url" />
| 字段 | 含义 | 示例 |
|---|---|---|
value | 指令绑定值 | 'admin' |
oldValue | 更新前的值 | 上一次权限 |
arg | 指令参数 | src |
modifiers | 修饰符对象 | { once: true } |
这些信息足够覆盖很多 DOM 行为配置。 但如果配置越来越复杂,可能说明它应该升级成组件。
四、指令和组件的边界要分清
组件适合封装结构、样式、状态和交互。 指令适合给已有元素附加低层 DOM 行为。 如果一个能力需要渲染额外 DOM、管理内部状态、暴露多个事件,通常应该写组件。
适合指令:
focus、click-outside、lazy、permission
适合组件:
Modal、Select、Table、Upload
例如弹窗不适合写成一个 v-modal 指令。
弹窗有遮罩、内容区、动画、插槽、关闭状态,明显是组件职责。
但“点击弹窗外部关闭”可以是指令。
五、指令和组合式函数的边界也不同
组合式函数封装响应式状态和逻辑。 自定义指令封装模板元素上的 DOM 行为。 两者有时可以配合,但不是同一类抽象。
function usePermission() {
const roles = ref(['admin']);
const hasRole = (role: string) => roles.value.includes(role);
return { roles, hasRole };
}
usePermission 适合在 JS 逻辑里判断权限。
v-permission 适合在模板上快速控制按钮显示。
一个管逻辑,一个管 DOM 表现,边界清楚才好维护。
六、自定义指令要注意 SSR 和清理
指令经常操作 window、document、el。
如果项目有 SSR,这些对象在服务端不存在,需要延迟到客户端钩子里执行。
此外,绑定事件、Observer、定时器后必须清理。
mounted:
addEventListener / observe / setInterval
unmounted:
removeEventListener / disconnect / clearInterval
记忆钩子:组件封装“东西”,组合式函数封装“逻辑”,指令封装“贴在 DOM 上的行为”。
这个边界能帮你避免把所有复用都写成指令。 面试官问“什么时候不用指令”时,就按这个边界回答。
七、常见误区与追问
- 误区:自定义指令可以替代组件。 指令适合 DOM 行为,复杂 UI 结构和状态应该用组件。
- 误区:指令里绑定事件不用清理。 指令如果注册全局事件、Observer 或定时器,必须在卸载时清理。
- 误区:权限指令就能保证安全。 前端权限指令只能控制展示,真正权限必须由后端接口校验。
- 追问:自定义指令有哪些常见场景? 自动聚焦、点击外部、图片懒加载、权限展示、拖拽、埋点曝光等。
- 追问:指令怎么接收参数? 通过 binding 的
value、arg、modifiers等字段接收配置。 - 追问:SSR 项目写指令要注意什么? 避免在服务端访问
window/document,DOM 操作放到客户端挂载阶段。
八、加强记忆
自定义指令这题用“DOM 行为复用”来记。它不是组件,也不是状态逻辑工具;组件封装 UI,composable 封装响应式逻辑,directive 封装元素行为。回答时用 v-focus 和 v-click-outside 举例,再补清理事件和权限指令不等于安全。