侧边栏

React项目中的几种CSS方案

发布于 | 分类于 前端/React

与Vue不同的是,React作为一个UI库,官方并没有提供推荐的CSS方案。我曾经接手过好几个React项目,由于团队不同,其中的CSS方案差异很大。

本文整理一下迄今为止React项目中常见的几种CSS方案。

inline style

基于React的组件,内联样式实际上是可行的,即直接在 JSX 元素中通过 style 属性编写样式,使用 JavaScript 对象表示 CSS。

jsx
<div style={{ color: 'red', padding: '10px' }}>Hello</div>

优点

  • 组件化:样式与组件紧密耦合
  • 动态样式方便:可直接通过 JavaScript 逻辑计算样式
  • 无类名冲突:样式天然隔离

缺点

  • 不支持伪类(:hover)、伪元素、媒体查询
  • 无法复用 CSS 特性(如 CSS 变量、动画)
  • 性能不如原生 CSS(大量动态样式时)

因此内联样式最要适用于简单组件、快速原型开发,或者某些需要动态计算样式及权重等场景。

normal css

常规的css写法,即将css代码放在一个或多个css文件中,这里包括less/scss等预处理器和postcss等后处理器对样式表的处理

常规css样式表最大的问题就是默认情况下无法避免样式冲突等问题,因此该方案一般不会在现代的React项目中使用。

class names

jsx非常灵活,在需要动态设置类名的情况下会编写额外的条件判断等逻辑

jsx
<button classNames={"a " + (flag ? 'b' : '') }>click me</button>

可以通过classnames这个库来实现,可以避免手动拼接动态样式名带来的额外编码工作

js
classNames('a', { b: true }) // 'a b'

css module

通过构建工具(Webpack/Vite)将 CSS 文件转换为局部作用域的类名。

css
/* Button.module.css */
.primary {
  background: blue;
  padding: 10px 20px;
}

然后这个css文件可以像一个js模块一样被引用,该模块将其中的类名作为键值暴露

jsx
import styles from './Button.module.css';

const Button = () => (
  <button className={styles.primary}>Click</button>
);
// 编译后的 HTML:
// <button class="Button_btn__1x2y3">Click</button>

优点:

  • 真正的 CSS:支持所有 CSS 特性
  • 自动作用域隔离:类名会被编译为唯一哈希值
  • 可复用性:支持 composes 组合样式

缺点:

  • 需要配置构建工具
  • 动态样式处理较麻烦
  • 全局样式仍需特殊处理(:global
  • 最终HTML标签上的样式名成是根据内容生成的hash,在生成环境需要借助sourcemap才能调试

CSS module适合中大型React项目、多人协作开发的团队,也是我在过去的React项目中见到的最多的CSS方案

css scoped

css scoped本身是Vue-loader基于SFC文件,提供的一种组件级别的样式解决方案,避免不同组件之间的类名冲突。

/* Button.scoped.css */
button {
  padding: 8px 16px;
}

.btn {
  background: blue;
}

对应的组件

jsx

复制
import './Button.scoped.css'; // 自动应用作用域

const Button = () => (
  <button className="btn">Click</button>
);

// 编译后的 HTML:
// <button data-v-123 class="btn">Click</button>
// 对应的 CSS 选择器会变成:button[data-v-123], .btn[data-v-123]

有点

  • 不需要修改类名,直接使用原生 CSS 选择器,相较于CSS Module更容易调试
  • 通过 data-v-xxx 属性实现作用域(与 Vue 相同原理)
  • 支持所有 CSS 特性,零运行时开销

缺点

  • 需要引入对应的postcss插件和loader,如scoped-css-loader

css in js

CSS-in-JS 是一种将 CSS 编写逻辑内置到 JavaScript 中的方式,使得样式和组件逻辑紧密结合。它的核心思想是通过 JavaScript 动态生成 CSS。

jsx
import styled from 'styled-components';

const StyledButton = styled.button`
  background: ${props => props.primary ? 'blue' : 'white'};
  padding: 10px 20px;
  &:hover {
    opacity: 0.9;
  }
`;

const App = () => (
  <StyledButton primary>Submit</StyledButton>
);

优点

  • 完美的样式隔离,自动生成唯一的 class 名称,防止样式冲突; 样式直接与组件绑定,避免了全局命名空间污染
  • 强大的动态样式能力,可以根据组件的状态(props 或 state)动态计算样式
  • 自动处理样式依赖,只有加载的组件会生成相关样式,减少不必要的 CSS
  • 自动添加浏览器前缀,处理兼容性问题等

缺点

  • 运行时开销,通常在组件加载或渲染时动态注入 <style> 标签,性能敏感场景需注意
  • 学习曲线较陡
  • 由于是运行时样式,可能存在不支持SSR的情况
  • 调试类名可读性差

主流的CSS-in-JS库有:styled-componentsEmotionJSS

  • Emotion: 支持模板字符串和对象样式。
  • Styled-Components: 专注于组件化样式管理, 使用ES6 的模板字符串语法来定义样式
  • JSS: 高度可扩展。

Styled System

Styled System 不是 CSS-in-JS 库,而是一个 基于 Style Props 的工具库,用于增强 styled-componentsEmotion 等的开发体验。

核心特点

  • 提供了一种 基于 props 传递样式 的方式。
  • 允许响应式设计(直接在 props 里定义断点)。
  • 通常用于构建 基于主题 的样式系统。
jsx
import styled from '@emotion/styled';
import { space, layout, color } from 'styled-system';

const Box = styled.div`
  ${space}
  ${layout}
  ${color}
`;

export default function App() {
  return <Box p={4} bg="blue" width={[1, 1 / 2, 1 / 4]}>Styled System</Box>;
}
  • 仍然依赖 CSS-in-JS,但用 props 代替传统的 styled 规则。
  • 适用于设计系统和 UI 组件库开发。

Vanilla Extract

Vanilla Extract一个支持 TypeScript 的零运行时 CSS-in-JS 方案,但它在 编译阶段生成 CSS,避免了运行时注入 CSS 的性能开销。

核心特点:

  • 编译时生成 CSS(类似于 CSS Modules),避免运行时性能开销。
  • 允许在 TypeScript 里定义样式,支持类型安全。
  • 生成 静态 CSS 文件,支持 Tree Shaking自动 CSS 提取
  • 兼容现代构建工具(Webpack、Vite、ESBuild)。
ts
// styles.css.ts
import { style } from '@vanilla-extract/css';

export const button = style({
  backgroundColor: 'blue',
  color: 'white',
  padding: '10px',
});

然后使用该样式

tsx
// App.tsx
import { button } from './styles.css';

export default function App() {
  return <button className={button}>Click me</button>;
}

区别:

  • 不依赖 JavaScript 运行时,只在 构建时 生成 CSS。
  • SSR 友好,不需要 styled-components 那样的 ServerStyleSheet 处理。
  • 更适合大型应用,可提升前端性能。

Utility-First CSS

原子类方案,通过预定义的实用类组合样式

html
<button className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
  Button
</button>

JSX可以将结构(HTML)和逻辑(JS)放在一些,但额外的CSS还需要单独去处理,频繁的文件切换会滞缓开发效率,CSS in JS在某种程度上可以解决这个问题,但我认为终极的解法就是原子类方案。

原子类可以让开发者在一个JSX文件中顺带将结构的样式一并处理了,开发心流不会中断,开发效率可以得到很大提高。

优点

  • 开发速度快:无需编写自定义 CSS,无需再考虑样式命名
  • 一致性:遵循设计系统的约束
  • 极小的 CSS 体积,不会有重复的样式、最终只产生项目中用到的样式

缺点:

  • HTML 类名冗长
  • 需要记忆类名约定,自定义配置需要学习
  • 由于没有语义化的类名,外部样式覆盖会比较麻烦

你要请我喝一杯奶茶?

版权声明:自由转载-非商用-保持署名和原文链接。

本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。