CSS 选型

高频版 · 来自面试实战和个人笔记

1 CSS 方案横向对比

原生 CSS + BEM、Sass/Less、CSS Modules、CSS-in-JS、Tailwind、UnoCSS、vanilla-extract 横向对比
原生 + BEM:无运行时,靠规范防污染,规模大时易崩。
Sass/Less:变量、嵌套、mixins,仍是全局样式,需配合命名规范。
CSS Modules:局部作用域(类名 hash),构建期处理,无运行时,企业稳态选择。
CSS-in-JS:样式与组件同文件,动态样式强,有运行时成本,适合强主题/组件库。
Tailwind:原子类,开发快、一致性高,类名长、学习曲线。
UnoCSS:按需生成,更轻更快,生态不如 Tailwind。
vanilla-extract:零运行时 CSS-in-TS,类型安全,构建期生成。

选型口诀:① 强动态/强主题 → CSS-in-JS 或 vanilla-extract ② 工程稳定+性能 → CSS Modules ③ 极致效率+一致性 → Tailwind/UnoCSS

各方案优缺点和适用场景
性能最好:原生 CSS、CSS Modules、SCSS(构建期)
动态能力最强:CSS-in-JS
开发效率最高:Tailwind / UnoCSS
团队协作最省心:CSS Modules、Tailwind
主题系统最强:CSS-in-JS、vanilla-extract

中大型业务常用:CSS Modules + 设计 token + 少量全局基建,综合最稳。

2 CSS Modules 详解

CSS Modules 用法和原理
文件命名为 *.module.css,导入后类名自动加 hash,实现局部作用域。
/* Button.module.css */
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
}
import styles from './Button.module.css';

<button className={styles.button}>Click me</button>
// 实际类名:Button_button__x7f3a
作用域隔离、:global、组合类名
作用域:默认局部,编译时类名加唯一 hash。
:global:定义全局样式,不参与 hash。
组合:classnames 或模板字符串组合多个类。
:global(.globalButton) {
  background-color: green;
}
className={`${styles.button} ${styles.large}`}
CSS Modules 缺乏动态样式的灵活性?
样式是预定义的,只能通过类名切换,无法像 CSS-in-JS 那样根据 props 动态生成颜色等。需用 inline style 或预定义多套类。

3 CSS-in-JS

Styled Components、Emotion 用法
Styled Components:模板字面量定义样式,支持 props 动态样式、ThemeProvider。
Emotion:更灵活,支持 css 函数、styled 组件,性能更好。
// Styled Components
const Button = styled.button`
  background-color: ${props => props.primary ? 'blue' : 'grey'};
  color: white;
  padding: 10px 20px;
`;

// Emotion
const buttonStyle = css`
  background-color: blue;
  color: white;
  padding: 10px 20px;
`;
<button css={buttonStyle}>Click Me</button>
Styled Components 为什么有性能问题?
运行时生成样式:每次渲染根据 props 动态生成 CSS,插入 style 标签。
重复类名:相同样式可能生成多份,增加 CSSOM 复杂度。
SSR 开销:服务端需生成并注入样式。
无法缓存:动态样式难以被浏览器缓存。

优化:减少动态 props、用 React.memo、避免 render 内创建 styled、考虑零运行时方案(Linaria、vanilla-extract)。

CSS-in-JS 解决了什么问题?
① 局部作用域,避免全局污染 ② 动态样式,直接用 JS 变量 ③ 样式与组件封装 ④ 按需生成,无冗余 ⑤ 支持 SSR。

4 Flex 布局

容器和项目、主轴和交叉轴
容器:display: flex 的元素;项目:容器的直接子元素。
主轴:main axis,由 flex-direction 决定(默认水平);交叉轴:cross axis,垂直于主轴。

容器属性:flex-direction、flex-wrap、flex-flow、justify-content、align-items、align-content

项目属性:order、flex-grow、flex-shrink、flex-basis、flex、align-self

flex-grow、flex-shrink、flex-basis
flex-grow:放大比例,默认 0。都为 1 时等分剩余空间;2 的占两倍。
flex-shrink:缩小比例,默认 1。空间不足时按比例缩小;0 则不缩小。
flex-basis:初始大小,默认 auto。

flex: 1 等价于 flex-grow: 1; flex-shrink: 1; flex-basis: 0%;

5 防全局污染方案

各方案如何避免样式冲突?
命名空间/BEM:类名前缀、Block__Element--Modifier,靠规范。
CSS Modules:编译时类名 hash,天然隔离。
Scoped CSS(Vue):自动加 data 属性,限制在当前组件。
Shadow DOM:完全隔离,内外互不影响。
CSS-in-JS:自动生成唯一类名,局部作用域。
/* BEM 示例 */
.button {}           /* Block */
.button__icon {}     /* Element */
.button--primary {}  /* Modifier */
Ant Design 如何处理全局类名污染?
BEM + ant- 前缀:类名如 ant-btn ant-btn-primary,降低冲突。
Less 变量定制:通过修改变量统一改主题,少直接覆盖类。
按需加载:只引入用到的组件样式。
部分组件用 @emotion/css:按需动态生成样式。