优雅使用 vue3.x + lodash 在项目中管理、封装指令 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
xiaodaiguaray
V2EX    前端开发

优雅使用 vue3.x + lodash 在项目中管理、封装指令

  •  
  •   xiaodaiguaray
    XiaoDaiGua-Ray 2023-09-22 15:16:15 +08:00 1048 次点击
    这是一个创建于 822 天前的主题,其中的信息可能已经有所发展或是发生改变。

    vue3.x directives

    vue 指令是一种特殊的 vue.js 特性,用于在 DOM 元素上添加特定的行为或功能。指令通过在元素上使用特定的指令名称和参数来实现,可以用于操作 DOM、响应事件、绑定数据等。今天简述一下如何在项目中优雅的管理、封装一些常用的指令。

    后面代码基本上会逐行进行注释,以便于大家阅读。

    管理指令

    统一管理

    1. 统一包管理:通过创建 directives 文件夹,我们可以将所有与指令相关的文件集中在一个位置,使其更易于查找和维护。这种统一的包管理结构有助于组织和管理指令相关的代码。
    2. 模块化指令:在 directives/modules 文件夹中,我们为每个指令创建一个独立的文件夹。这种模块化的结构使每个指令的逻辑和功能被封装在一个独立的文件中,方便单独管理和维护。每个指令文件夹中的出口文件 index.ts 可以用于导出指令逻辑,使其在项目中得以统一注册和使用。
    3. 规范化命名:指令文件夹的命名需要言简意赅,能够清晰表达指令的用途。通过规范化的命名,可以使项目中的指令更易于理解和识别,提高代码的可读性和可维护性。
    4. 可扩展性:通过这种管理方式,我们可以轻松地添加新的指令。只需在 directives/modules 文件夹中创建一个新的指令文件夹,并在其中编写相应的指令逻辑即可。这种结构使项目具有良好的可扩展性,方便我们根据需要添加、修改或删除指令。
     ├── directives ├──├── index.ts ├──├── modules ├──├──├── some directive ├──├──├──├── index.ts type.ts ... 

    统一注册

    每个指令都使用 index.ts 文件统一暴露一个 CustomDirectiveFC 类型的函数,并且在根目录的 index.ts 中自动搜集 modules 文件夹中的所有文件并注册这些指令。

    实现

    公共类型( type.ts )

    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 方法,即可完成自定义指令的搜集与注册。然后即可在项目全局中使用。

    自定义指令

    准备工作我们已经完成了,现在只需要开发自定义指令即可。现在我们来封装几个常用指令热热手。

    v-throttle

    实现

    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> 

    v-debounce

    实现

    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 ├──├──├── debunce ├──├──├──├── index.ts type.ts 

    按照上述步骤以后,你将会得到这样的一个目录结构。

    最后

    所有的代码源码都来自于 Ray Template,可以自行点击查看源码。如果觉得对您有帮助,也可以给模板点一个小星星~~~

    目前尚无回复
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     4884 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 25ms UTC 03:57 PVG 11:57 LAX 19:57 JFK 22:57
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86