关注点分离
众所周知,前端开发代码有三大部分:
- HTML 负责页面的结构
- CSS 负责页面的视觉样式
- JavaScript 负责页面的交互行为
这三大部分属于三种不同的"编程语言",有一定的隔离性,但在逻辑上又互相关联,因此如何组织这些代码,是前端开发的一个重要问题。在较早期的前端开发中是三种代码分离的,即HTML中只写标签,不写CSS和JavaScript,这两个在单独的区域/文件中实现。这种做法叫做"关注点分离"。
html
<!-- 三种代码合并 -->
<div style="color: red" onclick="console.log('click')"> 你好 </div>
<!-- 三种代码分离 -->
<div class="texts" onclick="handleClick()"> 你好 </div>
<style>
.texts {
color: red;
}
</style>
<script>
function handleClick() {
console.log('click');
}
</script>
关注点分离的代码结构清晰,每种代码的耦合性少,因此在早期受到推崇。但这种方式在代码组织上有很大的问题,即虽然三种技术互相独立,但在逻辑上却是相互关联的:
- CSS样式是和HTML标签关联的,标签绑定样式才能发挥效果
- JavaScript对数据和交互的处理会造成HTML元素的展示和CSS样式变化
尤其是前端交互逻辑和数据处理越来越重,将三类代码分离对开发有很多不便之处。试想如果要调整一个模块的逻辑和展示,需要三个甚至更多文件同时查看对比才能搞清楚,这会造成开发效率太低,逻辑不清晰,代码维护性差等很多问题。
组件化框架
后来出现了组件化的思想,将同一个模块的HTML, JavaScript, CSS代码放在一起,组成一个组件。组件本身可以自由组合和复用,组件内部的代码和外部隔离。开源社区中涌现了很多组件化的前端工具和框架,例如知名的React和Vue。现在这种组件化的模式,已经成为了前端开发的主流。
Vue模式
Vue框架中的单文件组件形式,既保留了HTML, JavaScript, CSS代码的独立性(虽然HTML部分演变为了生成HTML的模板),依旧保留关注点分离的特性,又将它们组合到了同一个文件中,HTML模板可以直接使用JavaScript的数据来更新元素。这样使得同属于一个组件的代码逻辑清晰展示,同时不会影响到其它组件。前面的代码使用Vue的单文件组件改写后如下:
vue
<script setup>
function handleClick() {
console.log('click');
}
</script>
<template>
<div class="texts" @click="handleClick"> 你好 </div>
</template>
<style>
.texts {
color: red;
}
</style>
React模式
React框架则首先发明了JSX语法,可以在JavaScript中编写标签模板。这样使得JavaScript和HTML的关系非常紧密:
jsx
import './index.css';
function Comp() {
const handleClick = () => {
console.log('click');
};
const styles = { fontSize: '14px' };
return <div className="texts" style={styles} onClick={handleClick}> 你好 </div>;
}
可以看到,React中一个函数即为一个组件,组件函数直接返回JSX标签语法,作为HTML渲染的模板。虽然Vue等组件后来也支持JSX,但还是React的使用最广泛。并且它们的标签语法都是类似于"HTML模板",而不是一种真正的HTML。
React中的CSS可以直接作为内联style属性的数据,在JavaScript中控制样式。React对于CSS的封装比较弱,因此开发者还是必须使用独立的CSS等样式文件来控制样式。例如:
css
/* index.css */
.texts {
color: red;
}
但这样事实上就造成了React组件中HTML和JavaScript的代码位置联系紧密,但与CSS的联系却有些松散。再加上在组件中引入的CSS规则不仅在组件内生效,而且对于页面全局都生效,这样会造成不同组件的样式冲突和污染。为了解决这些问题,使得CSS和组件紧密联系,开源社区中涌现了很多关于CSS的组件化技术。
组件化方案
前面我花了不少时间,根据技术发展的顺序学习了CSS组件化相关的技术,写了四篇文章:
- BEM、OOCSS、SMACSS、ITCSS、AMCSS、SUITCSS:CSS命名规范简介
- CSS Modules完全指南:CSS模块化的特性,生态工具和实践
- 运行时vs编译时:CSS in JS四种主流方案介绍和对比
- 从TailwindCSS到UnoCSS:原子化CSS框架接入、特性与配置
这四种方案分别是CSS命名规范/CSS Modules/CSS in JS/原子化CSS,它们都尝试着解决组件化开发中 CSS 的问题,我们在这里再简单描述一下。
CSS命名规范
CSS命名规范有非常多,最知名的是BEM命名规范。BEM的全称为Block Element Modifier,翻译成中文就是块,元素和修饰符。BEM使用这三种层级来规范CSS的命名:
- Block 区块 表示页面中一个独立可复用的模块或者组件
- Element 元素 表示区块中的一个组成元素
- Modifier 修饰符 修饰元素的状态或者行为
元素不能独立存在,必须依附于区块内。修饰符则必须跟在元素或者区块后面。因此可以这样组合命名:
block 单区块 block__element 区块+元素 block--modifier 区块+修饰符 block__element--modifier 区块+元素+修饰符
再列举一下其他的CSS命名规范:
- OOCSS: 面向对象的CSS,将CSS类封装为基类和子类等,例如分离结构和皮肤,分离容器和内容等
- SMACSS: 可扩展和模块化的CSS结构,主要将CSS规则分为五种类型:基础样式/布局样式/模块样式/状态样式/主题样式
- ITCSS: 把CSS规则分成了七层:Settings/Tools/Generic/Elements/Objects/Components/Trumps,并在不同的位置放置
- AMCSS: 使用HTML的属性key和值用来组织CSS选择器,有三种类型:Modules模块/Variations变体/Traits特征
- SUITCSS:组件化的样式工具,不仅包含CSS命名规范,也提供CSS预设包。有公共样式和组件样式等。
CSS Modules
CSS Modules中文叫做CSS模块。它通过自动的方式,完全避免组件内的类名与其它组件重复。默认情况下,我们定义的CSS类名标识符是全局的。使用CSS Modules之后,每个类名将变为唯一的全局名称,包含不会重复的哈希值。这里首先创建一个CSS文件,名称为App.module.css。
css
.class1 {
color: red;
}
.class2 {
color: blue;
}
:global(.class3) {
border: 1px solid yellow;
}
引入CSS文件时,我们可以拿到CSS文件导出的类名到全局名称的对应关系,从而在HTML中提供相应的类名。
jsx
import styles from './App.module.css';
import cn from 'classnames';
export default function App() {
return (
<div>
<div className={styles.class1}>test1</div>
<div className='class3'>test2</div>
<div className={cn(styles.class2, 'class3')}>test3</div>
</div>
)
}
CSS in JS
CSS in JS可以让我们在JavaScript中编写CSS代码,抛弃CSS文件。它的背后依然是通过JavaScript中写的CSS规则生成哈希类名,放到HTML元素的class上。
CSS in JS的工具非常多,主要分为两种类型:运行时和编译时。运行时可以在前端代码在用户访问时,动态生成CSS规则,优点是灵活,缺点是工具需要打包进代码,造成体积增大,且执行效率低一点。编译时是在打包时将所有CSS规则编译为类名,不允许执行时动态生成。
CSS in JS有许多使用方式,有模板字符串,style对象,css属性等。这里我们以Emotion为例,简单给出使用Demo:
jsx
// 模板字符串组件使用方式
import styled from "@emotion/styled";
const Div = styled.div`
color: red;
background: ${(props) => props.bg};
`;
function App() {
return (
<div>
<Div bg="blue">你好,jzplp</Div>
<Div bg="yellow">你好,jzplp</Div>
</div>
);
}
// style对象使用方式
import styled from "@emotion/styled";
const Div1 = styled.div((props) => {
return {
color: "red",
background: props.bg,
};
});
function App() {
return (
<div>
<Div1 bg="yellow">你好,jzplp1</Div1>
<Div1 bg="green">你好,jzplp2</Div1>
</div>
);
}
// css属性使用方式
function App() {
return (
<div
css={{ color: "red",
"&:hover": { background: "green" },
}}
>
你好 jzplp
</div>
);
}
原子化CSS
原子化CSS有点像一种编译时的CSS in JS:它是在编译时查找,生成对应的CSS规则,避免了直接写CSS文件。但与CSS in JS不同的是,它不需要写CSS语句,而是将一个一个CSS属性封装为预设的类名。我们代码中直接写类名即可。同时原子化CSS还支持扩展,例如主题,规则,变体等等。Tailwind CSS是最知名的原子化CSS工具,这里我们举例一下使用方法:
js
// 直接使用预设类名
function App() {
return <div className='text-xl font-bold text-orange-500'>jzplp1</div>;
}
不同的类名编译后效果不同,这里列举一些组合类名和编译后的CSS规则:
css
/* 类名:not-focus:bg-amber-500 */
.not-focus\:bg-amber-500 {
&:not(*:focus) {
background-color: var(--color-amber-500) /* oklch(76.9% 0.188 70.08) */;
}
}
/* 类名:sm:text-center */
.sm\:text-center {
@media (width >= 40rem) {
text-align: center;
}
}
/* 类名:bg-[red] */
.bg-\[red\] {
background-color: red;
}
特点和总结
前面我们根据技术发展的顺序,简单描述了CSS命名规范/CSS Modules/CSS in JS/原子化CSS。它们有些技术的出现是比组件化要早的,例如部分命名规范,但实际上也是和组件化一样解决类似的问题。这里我们简单对这些CSS组件化技术做一些特点和总结。
- 百花齐放,各种技术方案层出不穷
- 这里仅仅描述了四种主要技术,事实上CSS组件化的技术还有很多,例如Vue使用的组件作用域CSS
- 在每种技术类型内部,也有各种各样的工具和技术方案,例如有很多种CSS命名规范,也有很多种CSS in JS技术
- 优缺点明显,没有哪种技术能解决所有问题
- CSS命名规范是由人为规定方案,完全依赖于管理
- CSS Modules在避免类名冲突方面非常好,方案也被其它技术采用,但没解决组件化的其它问题
- CSS in JS成功将CSS代码与组件写在一起,但是代码繁琐(例如styled组件式写法)而且有性能损失
- 原子化CSS不需要写CSS代码,也实现了组件化,但不能运行时生成CSS规则,元素上太多类名时看起来也不直观
- 后面的新技术会吸取旧技术的优点
- CSS命名规范是由人手动约定类名避免重复,但CSS Modules直接由代码生成哈希类名,无重复的可能
- CSS in JS利用了CSS Modules中生成哈希类名的方式避免重复
- 原子化CSS则参考了编译时CSS in JS技术,在编译时生成CSS规则
虽然技术在不断的演进,但可以看到依然没有一种技术可以解决所有问题,每种技术的优缺点都很明显。这就造成了喜欢这些优点的开发者愿意用这些技术,而讨厌这些缺点的开发者就不愿意用这些技术。萝卜青菜,各有所爱。具体用哪种技术,还是要看具体项目的要求和约定形式。
对我自己来说,像是简单的小项目,原子化CSS非常不错。如果只是避免类名冲突,可以使用CSS Modules。至于CSS in JS我觉得有点麻烦,不太喜欢。如果是需要规范约定类名的项目,例如组件库等,可以参考BEM等CSS命名规范。
参考
- React 文档
react.docschina.org/ - Vue 文档
cn.vuejs.org/ - 单文件组件 Vue文档
cn.vuejs.org/guide/scali... - BEM、OOCSS、SMACSS、ITCSS、AMCSS、SUITCSS:CSS命名规范简介
jzplp.github.io/2026/css-na... - CSS Modules完全指南:CSS模块化的特性,生态工具和实践
jzplp.github.io/2026/css-mo... - 运行时vs编译时:CSS in JS四种主流方案介绍和对比
jzplp.github.io/2026/css-in... - 从TailwindCSS到UnoCSS:原子化CSS框架接入、特性与配置
jzplp.github.io/2026/atomic...