如果你使用过 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)变得安全又简单。
评论区
评论加载中...(永远不会加载完成的)