温州网站设计工作室,惠州网络推广哪里找,淘宝客建站教程,石家庄网站外包公司全局注册示例 1、创建指令 统一导出 src\directives\totalInputDirective.ts import type { Directive } from vue;// 使用 WeakMap 存储事件处理器#xff0c;避免直接在 DOM 元素上添加自定义属性 const handlerMap new WeakMapHTMLInputElement, (e: …全局注册示例1、创建指令 统一导出src\directives\totalInputDirective.tsimport type { Directive } from vue; // 使用 WeakMap 存储事件处理器避免直接在 DOM 元素上添加自定义属性 const handlerMap new WeakMapHTMLInputElement, (e: Event) void(); // 格式化输入的金额需定义在指令外部 const formatTotalInput (value: string): string { let v value; // 如果值为空设置为空字符串 if (!v) { return ; } // 1. 只允许数字、小数点、负号 v v.replace(/[^\d.-]/g, ); // 2. 处理负号只能出现在开头且只出现一次 let hasNegative false; if (v.includes(-)) { // 检查负号是否在开头 if (v[0] -) { hasNegative true; // 保留开头的负号移除其他位置的负号 v - v.slice(1).replace(/-/g, ); } else { // 负号不在开头移除所有负号 v v.replace(/-/g, ); } } // 3. 处理单独的小数点 if (v .) v 0.; if (v -.) v -0.; // 4. 处理前导零去除整数部分的前导零但保留小数点和负数的情况 if (hasNegative) { v v.replace(/^(-?)0(\d)/, $1$2); } else { v v.replace(/^0(\d)/, $1); } // 5. 去除多余的小数点只保留第一个 const firstDot v.indexOf(.); if (firstDot ! -1) { const beforeDot v.substring(0, firstDot 1); const afterDot v.substring(firstDot 1).replace(/\./g, ); v beforeDot afterDot; } // 6. 限制小数点后最多两位 const dotIndex v.indexOf(.); if (dotIndex ! -1) { const integerPart v.substring(0, dotIndex); const decimalPart v.substring(dotIndex 1, dotIndex 3); v integerPart . decimalPart; } // 7. 特殊情况处理如果只有负号或负号加0保留它们 // 8. 处理以0开头的非小数情况 if (v.length 1 v[0] 0 v[1] ! .) { v v.substring(1); } // 9. 返回格式化后的输入内容 return v; }; /** * 自定义指令金额输入指令只能输入数字、负号和小数点最多两位小数不能0开头不能小数点开头只能开头输入负号只能输入一次负号和小数点 */ export const totalInputDirective: Directive { /** * 指令挂载到元素上时的钩子函数 * param el - 指令绑定的DOM元素 */ mounted(el: HTMLElement | HTMLInputElement) { // 找到 input 元素 let input: HTMLInputElement | null null; if (el.tagName INPUT) { input el as HTMLInputElement; } else { input el.querySelector el.querySelector(input); } if (!input) return; // 处理输入事件的函数 const handler (e: Event) { const target e.target as HTMLInputElement; // 获取输入的值 const inputValue target.value; const value formatTotalInput(inputValue); if (value ! target.value) { const oldValue target.value; target.value value; // 如果值发生了变化触发input事件以确保Vue的数据绑定更新 if (oldValue ! value) { target.dispatchEvent(new Event(input, { bubbles: true })); } } }; // 监听输入事件 input.addEventListener(input, handler); // 使用 WeakMap 存储处理器引用以便后续移除 handlerMap.set(input, handler); }, /** * 指令从元素解绑时的钩子函数 * 清理事件监听器以避免内存泄漏 * param el - 指令绑定的DOM元素 */ unmounted(el: HTMLElement | HTMLInputElement) { let input: HTMLInputElement | null null; if (el.tagName INPUT) { input el as HTMLInputElement; } else { input el.querySelector el.querySelector(input); } if (input) { const storedHandler handlerMap.get(input); if (storedHandler) { // 移除事件监听器以防止内存泄漏 input.removeEventListener(input, storedHandler); handlerMap.delete(input); } } } };src\directives\index.tsexport * from ./totalInputDirective;2、注册指令2.1、全局注册指令导入指令 安装指令src\main.ts// 使用命名导入全局引入自定义指令金额输入指令 import { totalInputDirective } from /directives; // 创建 Vue 应用实例 const app createApp(App); // 安装自定义指令金额输入指令 totalInput在模板中使用 v-total-input 指令 app.directive(totalInput, totalInputDirective); // 将 Vue 应用实例挂载到 DOM 中的一个指定容器上从而启动 Vue 应用 app.mount(#app);2.2、局部注册指令导入指令 定义指令src\views\capital\CapitalInfo.vueimport { totalInputDirective } from /directives; // 定义指令金额输入指令规范以v开头在模板中使用 v-total-input 指令 const vTotalInput totalInputDirective ;3、使用指令src\views\capital\CapitalInfo.vue!-- 方法4自定义指令 blur -- el-input v-modellocalCapitalInfo.capitalTotal v-total-input blurhandleTotalBlur clearable /局局注册示例代码1、创建指令 统一导出src\directives\yearInputDirective.tsimport type { Directive } from vue; // 使用 WeakMap 存储事件处理器避免直接在 DOM 元素上添加自定义属性 const handlerMap new WeakMapHTMLInputElement, (e: Event) void(); /** * 自定义指令年份输入指令最多只能输入四位数字不能输入其他符号 * 该指令限制输入框只能输入最多4位数字字符通常用于年份输入场景 */ export const yearInputDirective: Directive { /** * 指令挂载到元素上时的钩子函数 * param el - 指令绑定的DOM元素 */ mounted(el: HTMLElement | HTMLInputElement) { // 找到 input 元素 let input: HTMLInputElement | null null; if (el.tagName INPUT) { input el as HTMLInputElement; } else { input el.querySelector el.querySelector(input); } if (!input) return; // 设置最大长度为4位数字 const maxLength 4; // 处理输入事件的函数过滤掉非数字字符并限制长度 const handler (e: Event) { const target e.target as HTMLInputElement; // 移除非数字字符(\D表示非数字)然后截取前maxLength个字符 const value target.value.replace(/\D/g, ).slice(0, maxLength); if (value ! target.value) { const oldValue target.value; target.value value; // 如果值发生了变化触发input事件以确保Vue的数据绑定更新 if (oldValue ! value) { target.dispatchEvent(new Event(input, { bubbles: true })); } } }; // 监听输入事件 input.addEventListener(input, handler); // 使用 WeakMap 存储处理器引用以便后续移除 handlerMap.set(input, handler); }, /** * 指令从元素解绑时的钩子函数 * 清理事件监听器以避免内存泄漏 * param el - 指令绑定的DOM元素 */ unmounted(el: HTMLElement | HTMLInputElement) { let input: HTMLInputElement | null null; if (el.tagName INPUT) { input el as HTMLInputElement; } else { input el.querySelector el.querySelector(input); } if (input) { const storedHandler handlerMap.get(input); if (storedHandler) { // 移除事件监听器以防止内存泄漏 input.removeEventListener(input, storedHandler); handlerMap.delete(input); } } } };src\directives\index.tsexport * from ./yearInputDirective;2、注册指令2.1、全局注册指令导入指令 安装指令src\main.ts// 使用命名导入全局引入自定义指令年份输入指令 import { yearInputDirective } from /directives; // 创建 Vue 应用实例 const app createApp(App); // 安装自定义指令年份输入指令 yearInput在模板中使用 v-year-input 指令 app.directive(yearInput, yearInputDirective); // 将 Vue 应用实例挂载到 DOM 中的一个指定容器上从而启动 Vue 应用 app.mount(#app);2.2、局部注册指令导入指令 定义指令src\views\capital\CapitalInfo.vueimport { yearInputDirective } from /directives; // 定义指令年份输入指令规范以v开头在模板中使用 v-year-input 指令 const vYearInput yearInputDirective;3、使用指令src\views\capital\CapitalInfo.vueel-row :gutter10 el-col :span24 el-form-item label资金年份 label-positionright el-input v-modelcapitalInfoStore.queryDTO.capitalYear clearable v-year-input / /el-form-item /el-col /el-rowVue3 TypeScript 自定义指令完整教程一、基础环境设置1. 确保 TypeScript 配置正确json// tsconfig.json { compilerOptions: { target: ES2020, module: ESNext, lib: [ES2020, DOM, DOM.Iterable], types: [vue, vite/client], strict: true, moduleResolution: bundler, resolveJsonModule: true, isolatedModules: true, esModuleInterop: true, noEmit: true, skipLibCheck: true, noUnusedLocals: true, noUnusedParameters: true }, include: [src/**/*.ts, src/**/*.d.ts, src/**/*.vue] }二、创建自定义指令的完整流程1. 创建基础指令带完整类型typescript// src/directives/NumberOnlyDirective.ts import type { Directive, DirectiveBinding } from vue // 1. 定义指令参数类型 interface NumberOnlyOptions { maxLength?: number min?: number max?: number allowNegative?: boolean allowDecimal?: boolean } // 2. 定义指令存储的扩展类型 declare global { interface HTMLElement { _numberOnlyHandler?: (e: Event) void _numberOnlyOptions?: NumberOnlyOptions } } // 3. 创建指令 export const NumberOnlyDirective: DirectiveHTMLElement, NumberOnlyOptions | number | undefined { /** * 元素挂载时调用 * param el - 绑定的元素 * param binding - 指令绑定信息 */ mounted(el: HTMLElement, binding: DirectiveBindingNumberOnlyOptions | number | undefined) { // 解析配置 const options parseOptions(binding.value) // 找到 input 元素 const input findInputElement(el) if (!input) { console.warn(NumberOnlyDirective: 未找到 input 元素) return } // 创建处理函数 const handler createInputHandler(input, options) // 存储引用 el._numberOnlyHandler handler el._numberOnlyOptions options // 绑定事件 input.addEventListener(input, handler) input.addEventListener(blur, () validateValue(input, options)) // 初始格式化 formatInputValue(input, options) }, /** * 参数更新时调用 */ updated(el: HTMLElement, binding: DirectiveBindingNumberOnlyOptions | number | undefined) { const newOptions parseOptions(binding.value) const input findInputElement(el) if (input el._numberOnlyHandler) { // 更新选项 el._numberOnlyOptions newOptions // 重新格式化当前值 formatInputValue(input, newOptions) } }, /** * 元素卸载时调用 */ unmounted(el: HTMLElement) { const input findInputElement(el) if (input el._numberOnlyHandler) { // 移除事件监听 input.removeEventListener(input, el._numberOnlyHandler) // 清理存储 delete el._numberOnlyHandler delete el._numberOnlyOptions } } } // 辅助函数 function parseOptions(value: NumberOnlyOptions | number | undefined): NumberOnlyOptions { const defaultOptions: NumberOnlyOptions { maxLength: 4, min: 0, max: 9999, allowNegative: false, allowDecimal: false } if (typeof value number) { return { ...defaultOptions, maxLength: value } } if (typeof value object value ! null) { return { ...defaultOptions, ...value } } return defaultOptions } function findInputElement(el: HTMLElement): HTMLInputElement | null { // 处理原生 input if (el.tagName.toLowerCase() input) { return el as HTMLInputElement } // 处理 Element Plus 的 el-input const input el.querySelector(input) if (input) return input // 处理 Element Plus 的特殊类名 const elInput el.querySelector(.el-input__inner) return elInput as HTMLInputElement } function createInputHandler(input: HTMLInputElement, options: NumberOnlyOptions): (e: Event) void { return (e: Event) { const target e.target as HTMLInputElement const cursorPos target.selectionStart || 0 const oldValue target.value // 根据配置过滤值 let newValue filterValue(target.value, options) // 限制长度 if (options.maxLength) { newValue newValue.slice(0, options.maxLength) } // 如果值发生变化 if (newValue ! oldValue) { // 更新值 target.value newValue // 调整光标位置 const newCursorPos calculateNewCursorPosition(oldValue, newValue, cursorPos) target.setSelectionRange(newCursorPos, newCursorPos) // 触发 Vue 更新 triggerVueUpdate(target) } } } function filterValue(value: string, options: NumberOnlyOptions): string { // 构建正则表达式 let pattern 0-9 if (options.allowNegative) { pattern -0-9 } if (options.allowDecimal) { pattern . } const regex new RegExp([^${pattern}], g) return value.replace(regex, ) } function calculateNewCursorPosition(oldValue: string, newValue: string, cursorPos: number): number { const lengthDiff newValue.length - oldValue.length return Math.max(0, Math.min(cursorPos lengthDiff, newValue.length)) } function formatInputValue(input: HTMLInputElement, options: NumberOnlyOptions) { const currentValue input.value const filteredValue filterValue(currentValue, options) if (filteredValue ! currentValue) { input.value filteredValue triggerVueUpdate(input) } } function validateValue(input: HTMLInputElement, options: NumberOnlyOptions) { const value input.value.trim() if (!value) return const numValue parseFloat(value) if (isNaN(numValue)) { input.value triggerVueUpdate(input) return } if (options.min ! undefined numValue options.min) { input.value options.min.toString() triggerVueUpdate(input) } if (options.max ! undefined numValue options.max) { input.value options.max.toString() triggerVueUpdate(input) } } function triggerVueUpdate(input: HTMLInputElement) { input.dispatchEvent(new Event(input, { bubbles: true })) input.dispatchEvent(new Event(change, { bubbles: true })) } // 导出类型方便在其他地方使用 export type { NumberOnlyOptions }2. 创建指令管理器可选typescript// src/directives/index.ts import type { App } from vue import { NumberOnlyDirective, type NumberOnlyOptions } from ./NumberOnlyDirective import { AutoFocusDirective } from ./AutoFocusDirective import { ClickOutsideDirective } from ./ClickOutsideDirective // 指令集合 const directives { number-only: NumberOnlyDirective, auto-focus: AutoFocusDirective, click-outside: ClickOutsideDirective } as const // 导出类型 export type DirectiveName keyof typeof directives // 全局安装函数 export function installDirectives(app: App) { Object.entries(directives).forEach(([name, directive]) { app.directive(name, directive) }) } // 导出单个指令用于局部注册 export { NumberOnlyDirective, AutoFocusDirective, ClickOutsideDirective } // 导出类型 export type { NumberOnlyOptions }3. 全局注册指令typescript// src/main.ts import { createApp } from vue import App from ./App.vue import { installDirectives } from ./directives const app createApp(App) // 安装所有指令 installDirectives(app) app.mount(#app)三、局部注册和使用1. 组件内局部注册vue!-- src/components/YearInput.vue -- template div !-- 使用自定义指令 -- el-input v-number-onlynumberOnlyOptions v-modelyearValue placeholder请输入年份 clearable / !-- 使用修饰符 -- el-input v-number-only.debouncenumberOnlyOptions v-modelcodeValue placeholder请输入验证码 / /div /template script setup langts import { ref, computed } from vue import { NumberOnlyDirective, type NumberOnlyOptions } from /directives // 局部注册指令 const vNumberOnly NumberOnlyDirective // 指令配置 const numberOnlyOptions computedNumberOnlyOptions(() ({ maxLength: 4, min: 1900, max: 2100, allowNegative: false, allowDecimal: false })) // 数据 const yearValue ref() const codeValue ref() /script style scoped /* 组件样式 */ /style2. 使用指令参数和修饰符typescript// src/directives/AdvancedNumberOnlyDirective.ts import type { Directive, DirectiveBinding } from vue // 定义修饰符类型 interface NumberOnlyModifiers { debounce?: boolean trim?: boolean lazy?: boolean immediate?: boolean } // 创建支持修饰符的指令 export const AdvancedNumberOnlyDirective: Directive HTMLElement, number | undefined, NumberOnlyModifiers { mounted(el, binding) { const { value, modifiers } binding console.log(指令参数:, value) console.log(修饰符:, modifiers) const input el.querySelector(input) if (!input) return // 根据修饰符应用不同逻辑 let handler createBaseHandler(input, value || 4) if (modifiers.debounce) { handler createDebouncedHandler(handler, 300) } if (modifiers.trim) { handler createTrimmedHandler(handler) } if (modifiers.lazy) { // 使用 change 事件而不是 input 事件 input.addEventListener(change, handler) el._handlerType change } else { input.addEventListener(input, handler) el._handlerType input } el._handler handler // 如果设置了 immediate立即格式化 if (modifiers.immediate) { formatInput(input, value || 4) } }, unmounted(el) { const input el.querySelector(input) if (input el._handler el._handlerType) { input.removeEventListener(el._handlerType, el._handler) } } } // 辅助函数 function createBaseHandler(input: HTMLInputElement, maxLength: number): (e: Event) void { return (e: Event) { const target e.target as HTMLInputElement target.value target.value.replace(/\D/g, ).slice(0, maxLength) triggerUpdate(target) } } function createDebouncedHandler(handler: Function, delay: number): Function { let timer: NodeJS.Timeout | null null return (...args: any[]) { if (timer) clearTimeout(timer) timer setTimeout(() { handler(...args) timer null }, delay) } } function createTrimmedHandler(handler: Function): Function { return (e: Event) { const target e.target as HTMLInputElement target.value target.value.trim() handler(e) } } function formatInput(input: HTMLInputElement, maxLength: number) { input.value input.value.replace(/\D/g, ).slice(0, maxLength) triggerUpdate(input) } function triggerUpdate(input: HTMLInputElement) { input.dispatchEvent(new Event(input, { bubbles: true })) } // 扩展类型 declare global { interface HTMLElement { _handler?: Function _handlerType?: input | change } }四、创建更多实用指令示例1. 自动聚焦指令typescript// src/directives/AutoFocusDirective.ts import type { Directive, DirectiveBinding } from vue interface AutoFocusOptions { delay?: number selectAll?: boolean } export const AutoFocusDirective: DirectiveHTMLElement, AutoFocusOptions | boolean | undefined { mounted(el, binding) { const options parseAutoFocusOptions(binding.value) const focusElement el.tagName.toLowerCase() input ? el as HTMLInputElement : el.querySelector(input, textarea, [tabindex]) if (!focusElement) return const focusFn () { focusElement.focus() if (options.selectAll select in focusElement) { ;(focusElement as HTMLInputElement).select() } } if (options.delay options.delay 0) { setTimeout(focusFn, options.delay) } else { // 使用 nextTick 确保 DOM 已更新 setTimeout(focusFn, 0) } } } function parseAutoFocusOptions(value: AutoFocusOptions | boolean | undefined): AutoFocusOptions { if (typeof value boolean) { return { delay: 0, selectAll: false } } if (typeof value object value ! null) { return { delay: value.delay || 0, selectAll: value.selectAll || false } } return { delay: 0, selectAll: false } }2. 点击外部关闭指令typescript// src/directives/ClickOutsideDirective.ts import type { Directive, DirectiveBinding } from vue export const ClickOutsideDirective: DirectiveHTMLElement, (e: MouseEvent) void { mounted(el, binding) { const handler (e: MouseEvent) { if (!el.contains(e.target as Node) el ! e.target) { binding.value(e) } } // 使用捕获阶段确保先执行 document.addEventListener(click, handler, true) // 存储引用以便清理 el._clickOutsideHandler handler }, unmounted(el) { if (el._clickOutsideHandler) { document.removeEventListener(click, el._clickOutsideHandler, true) delete el._clickOutsideHandler } } } declare global { interface HTMLElement { _clickOutsideHandler?: (e: MouseEvent) void } }3. 防抖指令typescript// src/directives/DebounceDirective.ts import type { Directive, DirectiveBinding } from vue type DebounceFunction (...args: any[]) void interface DebounceOptions { delay?: number events?: string immediate?: boolean } export const DebounceDirective: DirectiveHTMLElement, DebounceFunction, DebounceOptions { mounted(el, binding) { const { value: handler, modifiers, arg } binding if (typeof handler ! function) { console.warn(DebounceDirective: 绑定值必须是函数) return } const delay parseInt(arg || 300, 10) || 300 const events modifiers.events ? modifiers.events.split(,) : [input] const immediate modifiers.immediate || false // 创建防抖函数 let timeout: NodeJS.Timeout | null null let isImmediateCalled false const debouncedHandler (...args: any[]) { if (timeout) { clearTimeout(timeout) timeout null } if (immediate !isImmediateCalled) { handler(...args) isImmediateCalled true } else { timeout setTimeout(() { handler(...args) isImmediateCalled false }, delay) } } // 绑定事件 events.forEach(eventName { el.addEventListener(eventName.trim(), debouncedHandler) }) // 存储引用 el._debounceHandler debouncedHandler el._debounceEvents events }, unmounted(el) { if (el._debounceHandler el._debounceEvents) { el._debounceEvents.forEach(eventName { el.removeEventListener(eventName, el._debounceHandler!) }) delete el._debounceHandler delete el._debounceEvents } } } declare global { interface HTMLElement { _debounceHandler?: Function _debounceEvents?: string[] } }五、在组件中使用指令1. 使用多个指令vue!-- src/components/AdvancedForm.vue -- template div classform-container !-- 使用多个指令 -- el-input v-number-only{ maxLength: 4, min: 1900, max: 2100 } v-auto-focus{ delay: 100, selectAll: true } v-modelform.year placeholder请输入年份 / !-- 使用防抖指令 -- el-input v-modelform.search v-debounce:500handleSearch placeholder搜索防抖500ms / !-- 点击外部关闭 -- div v-click-outsidecloseDropdown classdropdown button clicktoggleDropdown打开菜单/button div v-ifisDropdownOpen classdropdown-content !-- 菜单内容 -- /div /div /div /template script setup langts import { ref } from vue // 局部注册指令 import { NumberOnlyDirective } from /directives/NumberOnlyDirective import { AutoFocusDirective } from /directives/AutoFocusDirective import { ClickOutsideDirective } from /directives/ClickOutsideDirective import { DebounceDirective } from /directives/DebounceDirective const vNumberOnly NumberOnlyDirective const vAutoFocus AutoFocusDirective const vClickOutside ClickOutsideDirective const vDebounce DebounceDirective // 数据 const form ref({ year: , search: }) const isDropdownOpen ref(false) // 方法 const handleSearch () { console.log(搜索:, form.value.search) // 执行搜索逻辑 } const toggleDropdown () { isDropdownOpen.value !isDropdownOpen.value } const closeDropdown () { isDropdownOpen.value false } /script style scoped .form-container { max-width: 400px; margin: 0 auto; padding: 20px; } .dropdown { position: relative; display: inline-block; margin-top: 20px; } .dropdown-content { position: absolute; background: white; border: 1px solid #ccc; padding: 10px; z-index: 1000; } /style六、测试自定义指令1. 创建单元测试typescript// tests/directives/NumberOnlyDirective.test.ts import { describe, it, expect, vi, beforeEach, afterEach } from vitest import { mount } from vue/test-utils import { defineComponent, ref, nextTick } from vue import { NumberOnlyDirective } from /directives/NumberOnlyDirective // 创建测试组件 const createTestComponent (template: string) { return defineComponent({ template, directives: { number-only: NumberOnlyDirective }, setup() { return { value: ref() } } }) } describe(NumberOnlyDirective, () { it(应该过滤非数字字符, async () { const wrapper mount(createTestComponent( input v-number-only v-modelvalue / )) const input wrapper.find(input) await input.setValue(abc123def) expect(input.element.value).toBe(123) expect(wrapper.vm.value).toBe(123) }) it(应该限制最大长度, async () { const wrapper mount(createTestComponent( input v-number-only{ maxLength: 4 } v-modelvalue / )) const input wrapper.find(input) await input.setValue(123456) expect(input.element.value).toBe(1234) expect(wrapper.vm.value).toBe(1234) }) it(应该允许负数和浮点数, async () { const wrapper mount(createTestComponent( input v-number-only{ allowNegative: true, allowDecimal: true } v-modelvalue / )) const input wrapper.find(input) await input.setValue(-123.45) expect(input.element.value).toBe(-123.45) expect(wrapper.vm.value).toBe(-123.45) }) it(应该在元素卸载时清理事件监听, async () { const wrapper mount(createTestComponent( input v-number-only v-modelvalue v-ifshow / )) const input wrapper.find(input) const removeEventListenerSpy vi.spyOn(input.element, removeEventListener) wrapper.vm.show false await nextTick() expect(removeEventListenerSpy).toHaveBeenCalled() }) })七、最佳实践和注意事项1. 命名规范typescript// 好的命名 vNumberOnly // 小写字母短横线连接 vAutoFocus // 描述清晰 vClickOutside // 表达意图 // 不好的命名 vNumber // 太泛泛 vOnly // 不清楚功能 vOut // 缩写不明确2. 性能优化typescript// 使用防抖/节流 import { throttle, debounce } from lodash-es const vOptimizedDirective { mounted(el, binding) { const handler binding.value // 使用防抖 const debouncedHandler debounce(handler, 300) // 或者使用节流 const throttledHandler throttle(handler, 300) el.addEventListener(input, debouncedHandler) el._handler debouncedHandler } } // 懒加载指令 const lazyDirective { mounted(el, binding) { // 使用 IntersectionObserver 实现懒加载 const observer new IntersectionObserver((entries) { entries.forEach(entry { if (entry.isIntersecting) { // 执行指令逻辑 binding.value() observer.unobserve(el) } }) }) observer.observe(el) el._observer observer }, unmounted(el) { if (el._observer) { el._observer.disconnect() } } }3. 错误处理typescriptconst vSafeDirective { mounted(el, binding) { try { // 指令逻辑 if (typeof binding.value ! function) { throw new Error(指令参数必须是函数) } // 执行指令 binding.value() } catch (error) { console.error(指令执行出错:, error) // 可以根据环境决定是否显示错误 if (process.env.NODE_ENV development) { el.style.border 2px solid red el.title 指令错误: ${error.message} } } } }八、总结学习要点理解指令生命周期mounted: 元素挂载时调用updated: 元素更新时调用unmounted: 元素卸载时调用掌握核心概念指令参数v-directive:argvalue修饰符v-directive.modifier绑定值binding.valueTypeScript 类型支持使用Directive泛型类型定义接口约束参数类型扩展 HTMLElement 类型最佳实践一个指令只做一件事提供适当的配置选项清理事件监听防止内存泄漏添加错误处理实际应用建议从简单开始先实现基础功能再逐步添加高级特性复用现有方案查看是否已有成熟的指令库编写测试确保指令的稳定性和可靠性文档化记录指令的使用方法和参数说明通过这个完整的教程你应该能够掌握 Vue3 TypeScript 中自定义指令的创建、注册和使用。记住实践是最好的学习方式尝试创建自己的指令来解决实际问题。