利用Vue元素指令自动合并tailwind类名

Tailwind CSS是一套将常用的CSS样式封装成类名的工具库。例如p-4表示设置元素的内边距为4,bg-red-500表示设置元素的背景颜色为红色,rounded-md表示将设置元素的边框圆角为中等大小。

在构建时,Tailwind会扫描项目中的HTML文件,提取使用到的类名,并生成对应的CSS样式,如:

复制代码

|---|----------------------------------------------|
| | .p-4 { padding: 1rem; } |
| | .bg-red-500 { background-color: #EF4444; } |
| | .rounded-md { border-radius: 0.375rem; } |

当同一个元素设置了多个相同属性的类名时,根据CSS的层叠规则,最后生成的CSS样式表中后出现的类名会覆盖前面出现的类名,而与HTML中类名的顺序无关,如:

复制代码

|---|---------------------------------------------------------|
| | <div class="text-blue-500 text-red-500">文本是红色的</div> |
| | <div class="text-red-500 text-blue-500">文本也是红色的</div> |

在实际应用中,我们往往希望在实例上覆盖先前设置的默认类,tailwind-merge库提供了twMerge函数,用于实现该功能。

具体来说,twMerge会将传入的类名字符串进行合并,对于同一属性的类名,只有最后一个出现的会被保留。例如:

复制代码

|---|---------------------------------------------------------------|
| | twMerge("text-blue-500 text-red-500") // 返回 "text-red-500" |
| | twMerge("text-red-500 text-blue-500") // 返回 "text-blue-500" |

通过使用twMerge,我们可以确保在动态生成类名时,最终应用的样式是我们期望的,而不受CSS样式表类名顺序的影响。

在Vue中使用twMerge

Vue提供了非常方便的类名传透机制,给组件实例指定的类名会自动传递给子组件的根元素,添加到子组件内部定义的类名列表末尾。

然而,对于Tailwind类名,这种简单的合并方式可能会导致样式冲突,如:

复制代码

|---|------------------------------------------------------------|
| | <!-- 子组件: CustomDiv.vue --> |
| | <template> |
| | <div class="bg-red-500 rounded-none"><slot></slot></div> |
| | </template> |

复制代码

|---|------------------------------------------------------------|
| | <!-- 在父组件中使用子组件 --> |
| | <CustomDiv class="bg-blue-500 rounded-md">演示</CustomDiv> |

在上述示例中,CustomDiv组件内部定义了bg-red-500rounded-none类,而父组件传递了bg-blue-500rounded-md类,最终的类名会被合并为bg-red-500 rounded-none bg-blue-500 rounded-md。实际渲染结果为红色背景(bg-red-500覆盖了bg-blue-500)、无圆角(rounded-none覆盖了rounded-md),而不是预期的蓝色背景、有圆角(bg-blue-500 rounded-md)。

为了处理这种情况,我们需要在Vue组件中手动使用twMerge函数来合并类名。

复制代码

|---|---------------------------------------------------------------------------------------------|
| | <!-- 子组件: CustomDiv.vue --> |
| | <template> |
| | <div :class="mergedClass"><slot></slot></div> |
| | </template> |
| | <script setup> |
| | import { computed } from 'vue' |
| | import { twMerge } from 'tailwind-merge' |
| | const props = defineProps({ class: String }) |
| | const mergedClass = computed(() => twMerge("bg-red-500 rounded-none", props.class || "")) |
| | </script> |

为了一个简单的功能,我们必须给每个组件都添加类似的代码来处理类名合并,这显然不是一个优雅的解决方案。

使用Vue指令自动应用twMerge

指令(Directive)是Vue提供的一种特殊语法,可以施加到HTML元素上,来对元素进行一些底层的操作。我们可以创建一个自定义指令来给元素自动应用twMerge函数,从而简化在组件中合并Tailwind类名的过程。

复制代码

|---|---------------------------------------------------------------------------------|
| | import { twMerge } from 'tailwind-merge' |
| | function mergeClassName(el) { |
| | if (!el.getAttribute('class')) return |
| | if (el.namespaceURI === 'http://www.w3.org/2000/svg') { |
| | el.setAttribute('class', twMerge(el.getAttribute('class') || "")) |
| | } else { |
| | el.className = twMerge(el.className) |
| | } |
| | } |
| | const twMergeDirective = { mounted: mergeClassName, updated: mergeClassName } |

使用方法

复制代码

|---|----------------------------------------------|
| | // 在Vue app中注册指令 |
| | app.directive("twMerge", twMergeDirective) |

之后就可以在HTML元素上使用v-tw-merge指令来自动应用twMerge函数了:

复制代码

|---|-----------------------------------------------------------------------------------------|
| | <div class="bg-red-500 bg-blue-500 rounded-none rounded-md">红色背景,无圆角</div> |
| | <div class="bg-red-500 bg-blue-500 rounded-none rounded-md" v-tw-merge>蓝色背景,有圆角</div> |
| | <CustomDiv class="bg-blue-500 rounded-md">原生: 红色背景,无圆角</CustomDiv> |
| | <CustomDiv class="bg-blue-500 rounded-md" v-tw-merge>v-tw-merge: 蓝色背景,有圆角</CustomDiv> |

渲染结果为

复制代码

|---|----------------------------------------------------------------------------------|
| | <div class="bg-red-500 bg-blue-500 rounded-none rounded-md">原生: 红色背景,无圆角</div> |
| | <div class="bg-blue-500 rounded-md">v-tw-merge: 蓝色背景,有圆角</div> |
| | <div class="bg-red-500 rounded-none bg-blue-500 rounded-md">原生: 红色背景,无圆角</div> |
| | <div class="bg-blue-500 rounded-md">v-tw-merge: 蓝色背景,有圆角</div> |

相关推荐
wu8587734575 分钟前
向量数据库不是银弹:从枚举漏检到 ReACT 多轮召回的实践路径
前端·数据库·react.js
meilindehuzi_a7 分钟前
深入理解 JavaScript 执行机制:从编译阶段到调用栈底层实现
开发语言·javascript·ecmascript
古怪今人9 分钟前
[前端]HTML盒模型与尺寸,标准文档流,块级元素、内联元素和行内块,CSS选择器
前端·css
小雨下雨的雨30 分钟前
基于鸿蒙PC Electron框架技术完成的表单验证技术详解
前端·javascript·华为·electron·前端框架·鸿蒙
提子拌饭13331 分钟前
饮料含糖量查询应用 - 鸿蒙PC用Electron框架完整实现
前端·javascript·华为·electron·前端框架·鸿蒙
JustHappy32 分钟前
古法编程秘籍(五):什么是进程和线程?从软件到 CPU 的一次完整旅程
前端·后端·代码规范
爱编程的小金40 分钟前
前端请求库的下一个进化方向:从 Promise 到策略化
前端·alova·前端请求库·请求策略
hsg7741 分钟前
简述:Jensen Huang‘s Footsteps网站全内容分析
前端·javascript·数据库
珑墨1 小时前
前端 AI 开发通用skill
前端
kyriewen1 小时前
一个人+Cursor,7天上线付费小程序:第1天我就想放弃了
前端·微信小程序·cursor