如果你使用过 shadcn/ui
或者一些高质量的 React 组件库,你很可能在 lib/utils.ts
文件中见过这样一个工具函数:
import { type ClassValue, clsx } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
这个 cn
函数看起来很简单,只是将 clsx
和 tailwind-merge
两个库包裹了一下。那么问题来了:我们为什么需要它?直接使用 clsx
不可以吗?
要回答这个问题,我们需要先了解这两个库各自的作用。
clsx
:优雅地拼接 Class
clsx
是一个非常小巧且高效的工具库,它的唯一作用就是帮助我们更方便、更优雅地拼接 CSS class 名称。在构建动态组件时,我们经常需要根据不同的状态或属性来决定是否应用某个 class,clsx
让这件事变得非常简单。
它支持多种数据类型作为参数:
import clsx from 'clsx';
// 字符串(可变参数)
clsx('foo', true && 'bar', 'baz');
//=> 'foo bar baz'
// 对象
clsx({ foo: true, bar: false, baz: isTrue() });
//=> 'foo baz'
// 数组
clsx(['foo', 0, false, 'bar']);
//=> 'foo bar'
// 混合使用
clsx('foo', [1 && 'bar', { baz: false, bat: null }, ['hello', ['world']]], 'cya');
//=> 'foo bar hello world cya'
可以看到,clsx
解决了条件性添加 class 的问题,代码可读性很高。但它有一个“问题”:它只负责拼接,不解决冲突。
tailwind-merge
:智能地解决冲突
在 Tailwind CSS 中,样式的冲突非常常见。例如,一个组件默认有 p-4
(padding: 1rem),但我们想在某个特定场景下覆盖它,传入 p-6
(padding: 1.5rem)。
如果我们直接用 clsx
会发生什么?
clsx('p-4', 'p-6');
//=> 'p-4 p-6'
最终的 class 会是 p-4 p-6
。这会导致最终渲染的样式取决于 CSS 文件中这两个 class 的定义顺序,这是一种不确定性,也是我们想要避免的。
tailwind-merge
就是为了解决这个问题而生的。它能够识别并理解 Tailwind 的 class,并智能地合并它们,保留最后那个有效的 class。
import { twMerge } from 'tailwind-merge';
twMerge('p-4 p-6');
//=> 'p-6'
twMerge('bg-red-500 bg-blue-500');
//=> 'bg-blue-500'
它会确保样式属性的唯一性,完美解决了冲突问题。
cn
:强强联合,最佳实践
现在我们可以回答最初的问题了:
为什么用
cn
而不是直接使用clsx
?
答案是:我们需要 clsx
的便利性来组织 class,也需要 tailwind-merge
的智能性来解决冲突。cn
函数将两者结合,提供了两全其美的解决方案。
让我们看一个完整的例子:
// 一个基础按钮组件
const Button = ({ className, ...props }) => {
const baseClasses = 'px-4 py-2 bg-blue-500 text-white rounded';
// 使用 cn 来合并基础样式和传入的自定义样式
return <button className={cn(baseClasses, className)} {...props} />;
}
// 使用组件时,覆盖部分样式
<Button className="bg-red-500 px-6" />
在这个例子中:
clsx
首先会将baseClasses
和className
拼接成一个长字符串:'px-4 py-2 bg-blue-500 text-white rounded bg-red-500 px-6'
。- 然后
tailwind-merge
会处理这个字符串,解决冲突:px-4
和px-6
冲突,保留px-6
。bg-blue-500
和bg-red-500
冲突,保留bg-red-500
。
- 最终生成的 class 是:
'py-2 text-white rounded bg-red-500 px-6'
。
这正是我们想要的结果!通过 cn
工具函数,我们既能享受 clsx
带来的灵活组合,又能确保 Tailwind CSS 样式的可预测性和正确性,让组件的样式覆盖(override)变得安全又简单。
评论区
评论加载中...