两个按钮用了同一个类名,结果样式全乱了?实战教你避坑!

156 阅读3分钟

一、场景重现:两个按钮,一个类名,一场“样式灾难”

我写了两个按钮组件,分别是 Button.jsxAnotherButton.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 />
    </>
  );
}

我以为两个按钮应该一个蓝、一个红,但实际上

错!最终两个按钮都变成了红色! 如图:

image.png

样式会被覆盖,以最后执行的样式渲染

这就是 全局样式冲突 的典型问题!


二、问题根源:CSS 是“全局”的!

CSS 是一种全局作用域语言。只要你写了一个类名 .button,它就会在整个项目中生效 —— 除非你手动加前缀、命名空间,或者使用更复杂的命名规则。

在这个例子中:

  • Button.cssAnotherButton.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.结果展示

3e967fffc6cd3da6b99a9b9ddc42b615.png

如图,已正常显示,但类名变成了_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;
}