React中使用classnames的案例

ClassNamesDem.tsx

javascript 复制代码
import React, { FC, useState } from 'react'
// 导入 classnames 库,通常简写为 cx 或 classNames
import classNames from 'classnames'
// 导入对应的 CSS 样式文件
import './ClassNamesDemo.css'

const ClassNamesDemo: FC = () => {
  // --- 状态定义 ---
  // 控制按钮是否处于激活状态
  const [isActive, setIsActive] = useState(false)
  
  // 控制按钮的主题颜色,使用 TypeScript 枚举类型限制取值
  const [theme, setTheme] = useState<'primary' | 'secondary' | 'danger'>('primary')
  
  // 控制按钮的尺寸大小
  const [size, setSize] = useState<'small' | 'medium' | 'large'>('medium')
  
  // 控制按钮是否被禁用
  const [isDisabled, setIsDisabled] = useState(false)

  // --- 事件处理函数 ---
  // 切换激活状态
  const toggleActive = () => setIsActive(!isActive)
  
  // 切换禁用状态
  const toggleDisabled = () => setIsDisabled(!isDisabled)
  
  // --- 核心逻辑:构建动态类名 ---
  // 使用 classNames 库组合类名,支持多种参数格式:
  // 1. 字符串:直接添加
  // 2. 模板字符串:动态拼接状态值
  // 3. 对象:键为类名,值为布尔条件(true则添加,false则忽略)
  const buttonClasses = classNames(
    'demo-button', // 基础类名,始终存在
    `demo-button--${theme}`, // 动态主题类名,例如 demo-button--primary
    `demo-button--${size}`,  // 动态尺寸类名,例如 demo-button--large
    {
      'demo-button--active': isActive,     // 仅当 isActive 为 true 时添加
      'demo-button--disabled': isDisabled  // 仅当 isDisabled 为 true 时添加
    }
  )

  return (
    <div className="demo-container">
      <h2>ClassNames 库使用示例</h2>
      
      {/* 控制面板:用于改变按钮状态的控件 */}
      <div className="controls">
        {/* 激活/取消激活按钮 */}
        <button 
          onClick={toggleActive}
          // 这里也演示了在子组件中直接使用 classNames
          className={classNames('control-button', { 'control-button--active': isActive })}
        >
          {/* 根据状态显示不同的按钮文本 */}
          {isActive ? '取消激活' : '激活'}
        </button>
        
        {/* 启用/禁用按钮 */}
        <button 
          onClick={toggleDisabled}
          className={classNames('control-button', { 'control-button--disabled': isDisabled })}
        >
          {isDisabled ? '启用' : '禁用'}
        </button>
        
        {/* 主题选择下拉框 */}
        <div className="selector-group">
          <label>主题:</label>
          <select 
            value={theme} 
            // 更新主题状态
            onChange={(e) => setTheme(e.target.value as any)}
            className="selector"
          >
            <option value="primary">Primary</option>
            <option value="secondary">Secondary</option>
            <option value="danger">Danger</option>
          </select>
        </div>
        
        {/* 尺寸选择下拉框 */}
        <div className="selector-group">
          <label>大小:</label>
          <select 
            value={size} 
            onChange={(e) => setSize(e.target.value as any)}
            className="selector"
          >
            <option value="small">Small</option>
            <option value="medium">Medium</option>
            <option value="large">Large</option>
          </select>
        </div>
      </div>
      
      {/* 演示区域:展示最终效果的按钮 */}
      <div className="demo-area">
        <button 
          // 应用上面构建好的动态类名
          className={buttonClasses}
          // React 的 disabled 属性,控制是否可点击
          disabled={isDisabled}
          // 点击事件
          onClick={() => alert(`当前按钮状态: ${isActive ? '激活' : '未激活'}`)}
        >
          动态样式按钮
        </button>
      </div>
      
      {/* 说明文字 */}
      <div className="explanation">
        <h3>ClassNames 使用说明:</h3>
        <ul>
          <li>根据状态动态组合CSS类名</li>
          <li>支持字符串、对象、数组等多种参数格式</li>
          <li>只有值为true的属性才会被添加到类名中</li>
          <li>避免了大量冗余的手动字符串拼接</li>
        </ul>
      </div>
    </div>
  )
}

export default ClassNamesDemo

ClassNamesDemo.css

复制代码
.demo-container {
  padding: 20px;
  border: 1px solid #ddd;
  margin: 20px 10px;
  font-family: Arial, sans-serif;
}

.demo-container h2 {
  color: #333;
  margin-top: 0;
}

.controls {
  display: flex;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 20px;
  padding: 15px;
  background-color: #f5f5f5;
  border-radius: 4px;
}

.control-button {
  padding: 8px 16px;
  border: 1px solid #ccc;
  background-color: #fff;
  cursor: pointer;
  border-radius: 4px;
  transition: all 0.3s;
}

.control-button:hover {
  background-color: #e6e6e6;
}

.control-button--active {
  background-color: #007bff;
  color: white;
  border-color: #007bff;
}

.control-button--disabled {
  background-color: #ccc;
  cursor: not-allowed;
}

.selector-group {
  display: flex;
  align-items: center;
  gap: 5px;
}

.selector {
  padding: 6px 10px;
  border: 1px solid #ccc;
  border-radius: 4px;
}

.demo-area {
  margin: 20px 0;
  text-align: center;
}

.demo-button {
  padding: 12px 24px;
  border: none;
  cursor: pointer;
  border-radius: 4px;
  font-weight: bold;
  transition: all 0.3s;
}

.demo-button--primary {
  background-color: #007bff;
  color: white;
}

.demo-button--secondary {
  background-color: #6c757d;
  color: white;
}

.demo-button--danger {
  background-color: #dc3545;
  color: white;
}

.demo-button--small {
  padding: 6px 12px;
  font-size: 12px;
}

.demo-button--medium {
  padding: 12px 24px;
  font-size: 16px;
}

.demo-button--large {
  padding: 18px 36px;
  font-size: 20px;
}

.demo-button--active {
  transform: scale(1.05);
  box-shadow: 0 4px 8px rgba(0,0,0,0.2);
}

.demo-button--disabled {
  opacity: 0.6;
  cursor: not-allowed;
  transform: none !important;
  box-shadow: none !important;
}

.explanation {
  margin-top: 30px;
  padding: 15px;
  background-color: #e9f7ef;
  border-radius: 4px;
}

.explanation h3 {
  margin-top: 0;
  color: #2c664f;
}

.explanation ul {
  margin: 10px 0;
  padding-left: 20px;
}

.explanation li {
  margin-bottom: 8px;
}

💡 核心知识点总结

为什么要用 classNames?

在 React 中,我们经常需要根据条件给元素添加类名。如果不使用这个库,代码可能会长这样:

javascript 复制代码
// 不好的写法:繁琐且容易出错
className={'demo-button ' + `demo-button--${theme}` + (isActive ? ' demo-button--active' : '')}

使用 classNames 可以让代码更清晰、更易读。

classNames 的参数格式:

字符串: classNames('foo', 'bar') => 'foo bar'

对象: classNames({ 'foo': true, 'bar': false }) => 'foo'

数组: classNames(['foo', 'bar']) => 'foo bar'

混合: 你可以像代码中那样把它们混合在一起使用。

TypeScript 类型断言 (as any):

在 onChange 事件中,为了简化示例,代码使用了 as any。在生产环境中,建议通过定义更精确的类型或使用受控组件的泛型来避免这种类型断言。

javascript 复制代码
classNames 的参数格式:
字符串: classNames('foo', 'bar') => 'foo bar'
对象: classNames({ 'foo': true, 'bar': false }) => 'foo'
数组: classNames(['foo', 'bar']) => 'foo bar'
混合: 你可以像代码中那样把它们混合在一起使用。
TypeScript 类型断言 (as any):
在 onChange 事件中,为了简化示例,代码使用了 as any。在生产环境中,建议通过定义更精确的类型或使用受控组件的泛型来避免这种类型断言。
相关推荐
果壳~12 小时前
【前端】【canvas】图片颜色填充工具实现详解
前端
Bigger12 小时前
Tauri (23)——为什么每台电脑位置显示效果不一致?
前端·rust·app
¥懒大王¥12 小时前
XSS-Game靶场教程
前端·安全·web安全·xss
ssshooter12 小时前
为什么移动端 safari 用 translate 移动元素卡卡的
前端·css·性能优化
闲云一鹤12 小时前
Claude Code 接入第三方AI模型(MiMo-V2-Flash)
前端·后端·claude
惜.己12 小时前
前端笔记(四)
前端·笔记
勤劳打代码12 小时前
循序渐进 —— Flutter GetX 状态管理
flutter·面试·前端框架
小北方城市网12 小时前
第 5 课:Vue 3 HTTP 请求与 UI 库实战 —— 从本地数据到前后端交互应用
大数据·前端·人工智能·ai·自然语言处理
踢球的打工仔12 小时前
ajax的基本使用(上传文件)
前端·javascript·ajax
樊小肆12 小时前
ollmam+langchain.js实现本地大模型简单记忆对话-内存版
前端·langchain·aigc