一、场景重现:两个按钮,一个类名,一场“样式灾难”
我写了两个按钮组件,分别是 Button.jsx 和 AnotherButton.jsx,它们都用了相同的类名 .button:
项目结构如图:
src/
├── components/
│ ├── Button.jsx
│ ├── button.css
│ ├── AnotherButton.jsx
│ └── anotherButton.css
├── App.jsx
└── main.jsx
Button.jsx
import './button.css';
function Button() {
return <button className="button">Button</button>;
}
button.css
.button {
background-color: blue;
color: white;
padding: 10px 20px;
}
AnotherButton.jsx
import './anotherButton.css';
function AnotherButton() {
return <button className="button">Another Button</button>;
}
anotherButton.css
.button {
background-color: red;
color: white;
padding: 10px 20px;
}
然后在 App.jsx 中同时使用这两个组件:
import Button from './components/Button';
import AnotherButton from './components/AnotherButton';
function App() {
return (
<>
<Button />
<AnotherButton />
</>
);
}
我以为两个按钮应该一个蓝、一个红,但实际上
❌ 错!最终两个按钮都变成了红色! 如图:
样式会被覆盖,以最后执行的样式渲染
这就是 全局样式冲突 的典型问题!
二、问题根源:CSS 是“全局”的!
CSS 是一种全局作用域语言。只要你写了一个类名 .button,它就会在整个项目中生效 —— 除非你手动加前缀、命名空间,或者使用更复杂的命名规则。
在这个例子中:
Button.css和AnotherButton.css都被引入;- 由于类名相同,后加载的样式会覆盖前面的样式;
- 导致两个按钮都应用了红色背景。
这就是前端开发中最让人头疼的“样式打架”问题之一。
三、解决方案:CSS 模块化登场!
为了解决这个问题,我们可以使用 CSS 模块化(CSS Modules)。
它通过局部作用域的方式,为每个类名生成唯一的标识符(通常是哈希值),从而避免类名冲突。
实战演示:用 CSS 模块化重构按钮组件
1.修改成模块化样式文件
button.module.css
//内容没变,就改了一个文件后缀 button.css->button.module.css
.button {
background-color: blue;
color: white;
padding: 10px 20px;
}
anotherButton.module.css
//内容没变,就改了一个文件后缀 anotherButton.css->anotherButton.module.css
.button {
background-color: red;
color: white;
padding: 10px 20px;
}
2. 修改组件代码
Button.jsx
//内容变了一点,import 和className={styles.button}改变了
import styles from './button.module.css';
const Button = () => {
return <button className={styles.button}>Button</button>;
};
export default Button;
AnotherButton.jsx
//内容变了一点,import 和className={styles.button}改变了
import styles from './anotherButton.module.css';
const AnotherButton = () => {
return <button className={styles.button}>Another Button</button>;
};
export default AnotherButton;
3. App.jsx 中使用组件
import Button from './components/Button';
import AnotherButton from './components/AnotherButton';
function App() {
return (
<>
<Button />
<AnotherButton />
</>
);
}
4.结果展示
如图,已正常显示,但类名变成了_button_XXXX,这是啥呢?
在开发环境(dev)中,我们依然可以看到 .button 这样的类名,便于调试:
<button class="button">Button</button>
<button class="button">Another Button</button>
但在构建(build)时,构建工具(如 Vite、Webpack)会自动为类名加上唯一 hash:
<button class="_button_1x2y3z">Button</button>
<button class="_button_4a5b6c">Another Button</button>
这样,两个按钮虽然在源码中都叫 .button,但在最终 DOM 中是完全不同的类名,样式互不干扰!
四、 CSS 模块化的核心优势
| 优势 | 说明 |
|---|---|
| 避免类名冲突 | 每个类名都有唯一 hash,不同组件即使同名也不会冲突 |
| 提升可维护性 | 类名与组件一一对应,样式修改定位更清晰 |
| 提升可读性(源码) | 开发阶段保留语义类名,不影响阅读 |
| 工程化友好 | 与 Vite、Webpack、React 等现代框架无缝集成 |
| 构建优化 | 生产环境自动优化类名,减少样式体积和命名冲突 |
五、免费送:CSS Modules 的一些小技巧
1. 支持多个类名绑定
import styles from './Button.module.css';
<button className={`${styles.button} ${styles.large}`}>大按钮</button>
2. 支持动态类名
const Button = ({ primary }) => {
return (
<button className={primary ? styles.primary : styles.default}>
动态按钮
</button>
);
};
3. 支持组合样式(Composes)
在 CSS Modules 中,你还可以通过 composes 实现样式复用:
/* Button.module.css */
.base {
padding: 10px 20px;
color: white;
}
.primary {
composes: base;
background-color: blue;
}
.default {
composes: base;
background-color: gray;
}