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。在生产环境中,建议通过定义更精确的类型或使用受控组件的泛型来避免这种类型断言。