目录

一句话理解 CVA

前 vs 后:看看用 CVA 多方便

❌ 不用 CVA(麻烦)

✅ 用 CVA(简单)

三步学会 CVA

第 1 步:安装

第 2 步:定义你的“按钮配方”

第 3 步:使用“配方”做按钮

实际例子:消息提示组件

进阶技巧:特殊组合

为什么要用 CVA?

一句话总结

有类似效果的工具clsx和twMerge

cn = clsx + twMerge

cn 一句话:

两个零件的分工:

2. twMerge(调解员)

为什么要一起用?

实际使用:

一句话总结:

cn 和 cva 的关系

一句话:

比喻:

用法对比:

什么时候用哪个?

一句话:


cva官方文档:Installation | cva

clsx官方文档:https://www.npmjs.com/package/clsx

tailwind-merge官方文档:https://www.npmjs.com/package/tailwind-merge

一句话理解 CVA

CVA 是帮你管好按钮样式的“秘书” - 你只需要告诉秘书“我要一个大的红色按钮”,秘书就会自动给你拼好所有需要的样式类。

前 vs 后:看看用 CVA 多方便

❌ 不用 CVA(麻烦)

// 每次都要手动拼类名
<button className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
  按钮
</button>
<button className="px-2 py-1 bg-red-500 text-white rounded hover:bg-red-600">
  按钮
</button>
<button className="px-6 py-3 bg-gray-500 text-white rounded hover:bg-gray-600">
  按钮
</button>

✅ 用 CVA(简单)

// 一次定义,到处使用
<Button size="md" color="blue">按钮</Button>
<Button size="sm" color="red">按钮</Button>
<Button size="lg" color="gray">按钮</Button>

三步学会 CVA

第 1 步:安装

npm install class-variance-authority

第 2 步:定义你的“按钮配方”

import { cva } from 'class-variance-authority'

// 想象成创建“按钮配方”
const buttonRecipe = cva(
  // 基础材料(所有按钮都有的)
  ['font-bold', 'rounded', 'transition'],  // 字体粗 + 圆角 + 动画
  
  {
    // 可选的“调料包”
    variants: {
      // 颜色调料包
      color: {
        blue: 'bg-blue-500 text-white hover:bg-blue-600',
        red: 'bg-red-500 text-white hover:bg-red-600',
        gray: 'bg-gray-500 text-white hover:bg-gray-600'
      },
      // 尺寸调料包
      size: {
        sm: 'px-2 py-1 text-sm',    // 小号
        md: 'px-4 py-2 text-base',  // 中号
        lg: 'px-6 py-3 text-lg'     // 大号
      }
    },
    
    // 默认调料(不指定时用的)
    defaultVariants: {
      color: 'blue',
      size: 'md'
    }
  }
)

第 3 步:使用“配方”做按钮

// React 组件中使用
function Button({ color, size, children }) {
  // 厨师(cva)按配方做菜
  const className = buttonRecipe({ color, size })
  
  return (
    <button className={className}>
      {children}
    </button>
  )
}

// 使用的时候超简单!
export default function App() {
  return (
    <div>
      {/* 中号蓝色按钮(默认) */}
      <Button>普通按钮</Button>
      
      {/* 小号红色按钮 */}
      <Button size="sm" color="red">删除</Button>
      
      {/* 大号灰色按钮 */}
      <Button size="lg" color="gray">大按钮</Button>
    </div>
  )
}

实际例子:消息提示组件

// 1. 定义“消息配方”
const messageRecipe = cva(
  ['p-4', 'rounded-lg', 'border'],  // 所有消息都有的
  
  {
    variants: {
      type: {
        success: 'bg-green-100 border-green-300 text-green-800',
        error: 'bg-red-100 border-red-300 text-red-800',
        warning: 'bg-yellow-100 border-yellow-300 text-yellow-800',
        info: 'bg-blue-100 border-blue-300 text-blue-800'
      }
    },
    defaultVariants: {
      type: 'info'  // 默认是信息类型
    }
  }
)

// 2. 创建组件
function Message({ type, children }) {
  return (
    <div className={messageRecipe({ type })}>
      {children}
    </div>
  )
}

// 3. 使用
<Message type="success">操作成功!</Message>
<Message type="error">出错了!</Message>
<Message>普通提示</Message>  {/* 默认是 info */}

进阶技巧:特殊组合

有时候某些组合需要特殊处理:

const buttonRecipe = cva(['btn'], {
  variants: {
    color: { blue: 'bg-blue-500', red: 'bg-red-500' },
    disabled: { true: 'opacity-50', false: '' }
  },
  // 特殊组合:当 disabled=true 且 color=red 时,加额外样式
  compoundVariants: [
    {
      color: 'red',
      disabled: true,
      class: 'border-2 border-red-300'  // 额外加个边框
    }
  ]
})

为什么要用 CVA?

不用 CVA 用了 CVA
样式分散各处 样式集中管理
容易写错类名 不容易出错
修改要到处找 改一个地方就行
没有类型提示 有智能提示

一句话总结

CVA = 样式字典 + 自动拼装。你定义好“大红色按钮”的配方,以后只需要说“我要大红色按钮”,CVA 自动帮你拼好所有样式类。

试试看,写一次配方,享受永远的方便!

有类似效果的工具clsx和twMerge

import { clsx, type ClassValue } from 'clsx';
import { twMerge } from 'tailwind-merge';

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

cn = clsx + twMerge

cn 一句话:

一个帮你 智能合并类名 + 解决样式冲突 的工具函数。

两个零件的分工:

1. clsx(合并员)

// 作用:把各种格式的类名合并成一个字符串
clsx('a', 'b')                     // → 'a b'
clsx('a', false && 'b')            // → 'a'(自动过滤false)
clsx('a', { 'b': true, 'c': false }) // → 'a b'

2. twMerge(调解员)

// 作用:解决Tailwind类名冲突(留最新的)
twMerge('p-4 p-8')      // → 'p-8'(p-4被移除)
twMerge('text-red text-blue') // → 'text-blue'

为什么要一起用?

// ❌ 只有clsx:冲突无法解决
clsx('p-4', 'p-8')      // → 'p-4 p-8'(冲突!两个padding都生效?)

// ❌ 只有twMerge:条件判断麻烦
twMerge('p-4', isLarge && 'p-8') // 条件写法不方便

// ✅ cn = clsx + twMerge(完美!)
cn('p-4', isLarge && 'p-8')     // → 自动:条件判断+冲突解决

实际使用:

// 定义cn函数(一次,到处用)
import { clsx } from 'clsx'
import { twMerge } from 'tailwind-merge'

function cn(...inputs) {
  return twMerge(clsx(inputs))  // 先合并,再解决冲突
}

// 使用
<button className={cn(
  'px-4 py-2',           // 基础
  isActive && 'bg-blue', // 条件样式
  'text-white',          // 固定样式
  disabled && 'opacity-50', // 另一个条件
  className              // 外部传入的(也能处理冲突)
)}>
  按钮
</button>

一句话总结:

cn()代替手动拼接类名,让你写Tailwind样式更干净、不冲突!

cn 和 cva 的关系

一句话:

CVA 管“设计规范”,cn 管“临时调整”。

比喻:

  • CVA = 公司统一的工服(有固定款式)

  • cn = 天冷时自己加件外套(临时搭配)

用法对比:

// CVA:定义标准按钮(设计系统)
const button = cva(['btn'], {
  variants: {
    size: { sm: 'px-2', md: 'px-4' },
    color: { blue: 'bg-blue-500', red: 'bg-red-500' }
  }
})

// cn:临时加样式(特殊情况)
<button className={cn(
  button({ size: 'md', color: 'blue' }),  // CVA的基础
  'mt-4',                                 // cn加的间距
  isLoading && 'opacity-50'               // cn加的条件样式
)}>
  提交
</button>

什么时候用哪个?

场景 用哪个 例子
统一设计规范 CVA 按钮/卡片/表单的标准样式
临时微调 cn 加个边距、特殊状态
既有规范又有特殊 CVA + cn 标准按钮,但这次需要更多上边距

一句话:

先用 CVA 定规范,再用 cn 做微调。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐