React项目中的几种CSS方案
与Vue不同的是,React作为一个UI库,官方并没有提供推荐的CSS方案。我曾经接手过好几个React项目,由于团队不同,其中的CSS方案差异很大。
本文整理一下迄今为止React项目中常见的几种CSS方案。
inline style
基于React的组件,内联样式实际上是可行的,即直接在 JSX 元素中通过 style
属性编写样式,使用 JavaScript 对象表示 CSS。
<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非常灵活,在需要动态设置类名的情况下会编写额外的条件判断等逻辑
<button classNames={"a " + (flag ? 'b' : '') }>click me</button>
可以通过classnames这个库来实现,可以避免手动拼接动态样式名带来的额外编码工作
classNames('a', { b: true }) // 'a b'
css module
通过构建工具(Webpack/Vite)将 CSS 文件转换为局部作用域的类名。
/* Button.module.css */
.primary {
background: blue;
padding: 10px 20px;
}
然后这个css文件可以像一个js模块一样被引用,该模块将其中的类名作为键值暴露
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;
}
对应的组件
复制
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。
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-components
、Emotion
、JSS
等
- Emotion: 支持模板字符串和对象样式。
- Styled-Components: 专注于组件化样式管理, 使用ES6 的模板字符串语法来定义样式
- JSS: 高度可扩展。
Styled System
Styled System
不是 CSS-in-JS 库,而是一个 基于 Style Props 的工具库,用于增强 styled-components
、Emotion
等的开发体验。
核心特点:
- 提供了一种 基于 props 传递样式 的方式。
- 允许响应式设计(直接在
props
里定义断点)。 - 通常用于构建 基于主题 的样式系统。
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)。
// styles.css.ts
import { style } from '@vanilla-extract/css';
export const button = style({
backgroundColor: 'blue',
color: 'white',
padding: '10px',
});
然后使用该样式
// 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
原子类方案,通过预定义的实用类组合样式
<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 类名冗长
- 需要记忆类名约定,自定义配置需要学习
- 由于没有语义化的类名,外部样式覆盖会比较麻烦
你要请我喝一杯奶茶?
版权声明:自由转载-非商用-保持署名和原文链接。
本站文章均为本人原创,参考文章我都会在文中进行声明,也请您转载时附上署名。
