vue3.x directives
vue
指令是一种特殊的 vue.js
特性,用于在 DOM
元素上添加特定的行为或功能。指令通过在元素上使用特定的指令名称和参数来实现,可以用于操作 DOM
、响应事件、绑定数据等。今天简述一下如何在项目中优雅的管理、封装一些常用的指令。
后面代码基本上会逐行进行注释,以便于大家阅读。
directives
文件夹,我们可以将所有与指令相关的文件集中在一个位置,使其更易于查找和维护。这种统一的包管理结构有助于组织和管理指令相关的代码。directives/modules
文件夹中,我们为每个指令创建一个独立的文件夹。这种模块化的结构使每个指令的逻辑和功能被封装在一个独立的文件中,方便单独管理和维护。每个指令文件夹中的出口文件 index.ts
可以用于导出指令逻辑,使其在项目中得以统一注册和使用。·
├── directives
├──├── index.ts
├──├── modules
├──├──├── some directive
├──├──├──├── index.ts type.ts ...
每个指令都使用 index.ts
文件统一暴露一个 CustomDirectiveFC
类型的函数,并且在根目录的 index.ts
中自动搜集 modules
文件夹中的所有文件并注册这些指令。
import type { Directive } from 'vue'
import type { App } from 'vue'
export type { DebounceBindingOptions } from './modules/debounce/type'
export type { ThrottleBindingOptions } from './modules/throttle/type'
export type CustomDirectiveFC<T, K> = () => Directive<T, K>
export interface DirectiveModules extends Object {
default: CustomDirectiveFC<unknown, unknown>
}
export type AppType = App<Element>
利用 import.meta.glob
方法(如果是 webpack
则是使用 require.context
),获取所有指令文件夹的路径。然后,遍历这些文件夹,找到包含 index.ts
文件的位置。在每个 index.ts
文件中,你可以导入 CustomDirectiveFC
函数。通过这种方式,你可以自动搜集所有指令,并进行注册。
警告 该方式会搜集
modules
中所有的文件夹,并且以文件名称作为指令名称。如果是多个单词,请使用小写单词并以-
连接。
import type { DirectiveModules, CustomDirectiveFC } from '@/directives/type'
export const combineDirective = <
T extends Record<string, DirectiveModules>,
K extends keyof T,
>(
directiveModules: T,
) => {
const directives = Object.keys(directiveModules).reduce((pre, curr) => {
const fc = directiveModules[curr]?.default
if (typeof fc === 'function') {
pre[curr] = fc
return pre
} else {
throw new Error('directiveModules[curr] is not function')
}
}, {} as Record<K, CustomDirectiveFC<unknown, unknown>>)
return directives
}
import { combineDirective } from './helper/combine'
import { forIn } from 'lodash-es'
import type { App } from 'vue'
import type { DirectiveModules } from '@/directives/type'
/**
*
* 初始化全局自定义指令
*
* 该方法会将 modules 下每个文件夹视为一个指令
* 并且会将文件夹名称识别为指令名称
* 每个文件下的 index.ts 文件视为每个指令的入口(也就是指令的处理逻辑, 需要暴露出一个 Directive 类型的对象)
*/
export const setupDirectives = (app: App<Element>) => {
// 获取 modules 包下所有的 index.ts 文件
const directiveRawModules: Record<string, DirectiveModules> =
import.meta.glob('@/directives/modules/**/index.ts', {
eager: true,
})
// 将所有的包提取出来(./modules/[file-name]/index.ts)
const directivesModules = combineDirective(directiveRawModules)
// 提取文件名(./modules/copy/index.ts => copy)
const regexExtractDirectiveName = /(?<=modules\/).*(?=\/index\.ts)/
// 匹配合法指令名称
const regexDirectiveName = /^([^-]+-)*[^-]+$/
forIn(directivesModules, (value, key) => {
const dname = key.match(regexExtractDirectiveName)?.[0]
if (typeof dname === 'string' && regexDirectiveName.test(dname)) {
app.directive(dname, value?.())
} else {
console.error(`[setupDirectives] ${dname} is not a valid directive name`)
}
})
}
在 main.ts
文件中导入并且调用 setupDirectives
方法,即可完成自定义指令的搜集与注册。然后即可在项目全局中使用。
准备工作我们已经完成了,现在只需要开发自定义指令即可。现在我们来封装几个常用指令热热手。
import type { ThrottleSettings } from 'lodash-es'
import type { AnyFC } from '@/types/modules/utils'
export interface ThrottleBindingOptions {
func: AnyFC
trigger?: string
wait?: number
options?: ThrottleSettings
}
import { throttle } from 'lodash-es'
import { on, off } from '@use-utils/element'
import type { ThrottleBindingOptions } from './type'
import type { AnyFC } from '@/types/modules/utils'
import type { DebouncedFunc } from 'lodash-es'
import type { CustomDirectiveFC } from '@/directives/type'
const throttleDirective: CustomDirectiveFC<
HTMLElement,
ThrottleBindingOptions
> = () => {
let throttleFunction: DebouncedFunc<AnyFC> | null
return {
beforeMount: (el, { value }) => {
const { func, trigger = 'click', wait = 500, options } = value
if (typeof func !== 'function') {
throw new Error('throttle directive value must be a function')
}
throttleFunction = throttle(func, wait, Object.assign({}, options))
on(el, trigger, throttleFunction)
},
beforeUnmount: (el, { value }) => {
const { trigger = 'click' } = value
if (throttleFunction) {
throttleFunction.cancel()
off(el, trigger, throttleFunction)
}
throttleFunction = null
},
}
}
export default throttleDirective
<template>
<p>我执行了{{ count }}次</p>
<p>该方法 1s 内仅会执行一次</p>
<button
v-throttle="{
func: handleClick,
trigger: 'click',
wait: 1000,
options: {},
}"
>
节流按钮
</button>
</template>
<script setup lang="ts">
const count = ref(0)
const handleClick = () => {
count.value++
}
</script>
import type { DebounceSettings } from 'lodash-es'
import type { AnyFC } from '@/types/modules/utils'
export interface DebounceBindingOptions {
func: AnyFC
trigger?: string
wait?: number
options?: DebounceSettings
}
import { debounce } from 'lodash-es'
import { on, off } from '@use-utils/element'
import type { DebounceBindingOptions } from './type'
import type { AnyFC } from '@/types/modules/utils'
import type { DebouncedFunc } from 'lodash-es'
import type { CustomDirectiveFC } from '@/directives/type'
const debounceDirective: CustomDirectiveFC<
HTMLElement,
DebounceBindingOptions
> = () => {
let debounceFunction: DebouncedFunc<AnyFC> | null
return {
beforeMount: (el, { value }) => {
const { func, trigger = 'click', wait = 500, options } = value
if (typeof func !== 'function') {
throw new Error('debounce directive value must be a function')
}
debounceFunction = debounce(func, wait, Object.assign({}, options))
on(el, trigger, debounceFunction)
},
beforeUnmount: (el, { value }) => {
const { trigger = 'click' } = value
if (debounceFunction) {
debounceFunction.cancel()
off(el, trigger, debounceFunction)
}
debounceFunction = null
},
}
}
export default debounceDirective
<template>
<p>我执行了{{ count }}次</p>
<p>该方法将延迟 1s 执行</p>
<button
v-throttle="{
func: handleClick,
trigger: 'click',
wait: 1000,
options: {},
}"
>
防抖按钮
</button>
</template>
<script setup lang="ts">
const count = ref(0)
const handleClick = () => {
count.value++
}
</script>
·
├── directives
├──├── index.ts type.ts
├──├── modules
├──├──├── throttle
├──├──├──├── index.ts type.ts
├──├──├── debounce
├──├──├──├── index.ts type.ts
按照上述步骤以后,你将会得到这样的一个目录结构。
所有的代码源码都来自于 Ray Template,可以自行点击查看源码。如果觉得对您有帮助,也可以给模板点一个小星星~~~