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。在生产环境中,建议通过定义更精确的类型或使用受控组件的泛型来避免这种类型断言。
相关推荐
简单的话*42 分钟前
Logback 日志按月归档并保留 180 天,超期自动清理的配置实践
java·前端·python
困惑阿三1 小时前
深入理解 JavaScript 中的(Promise.race)
开发语言·前端·javascript·ecmascript·reactjs
我命由我123451 小时前
微信小程序 bind:tap 与 bindtap 的区别
开发语言·前端·javascript·微信小程序·小程序·前端框架·js
5335ld1 小时前
vue2 直播地址播放 兼容浏览器
前端·vue.js
克喵的水银蛇1 小时前
Flutter 布局实战:掌握 Row/Column/Flex 弹性布局
前端·javascript·flutter
哆啦A梦15881 小时前
60 订单页选择收货地址
前端·javascript·vue.js·node.js
Hilaku1 小时前
利用 link rel="prefetch":如何让用户的页面秒开?
前端·javascript·性能优化
Apifox1 小时前
如何通过抓包工具快速生成 Apifox 接口文档?
前端·后端·测试
没事多睡觉6661 小时前
JavaScript 中 this 指向教程
开发语言·前端·javascript