css modules + bem 是 react 18 下最稳的样式方案,需严格遵循文件名与组件名一致(pascalcase)、类名以 block 名开头、element 用双下划线、jsx 用字符串键访问,并集成 postcss-bem-linter 强制校验。

SSR 或微前端项目直接绕开 styled-components
React 18 的并发渲染特性对样式注入时序更敏感,而 styled-components 依赖运行时动态插入 <style></style> 标签。一旦服务端渲染(SSR)未配 ServerStyleSheet,或微前端主应用启用 strictStyleIsolation(如 qiankun),就会出现 Warning: Prop `className` did not match,甚至样式完全不生效。
常见错误现象不是组件没写对,而是:服务端生成的类名哈希和客户端不一致;子应用样式被主框架沙箱拦截;构建后静态 HTML 中无任何样式规则。
- SSR 项目必须验证
getInitialProps或getServerSideProps中是否包裹了sheet.collectStyles()和sheet.getStyleTags() - 微前端场景下,
styled-components需显式用StyleSheetManager指定容器节点,否则样式注入到document.head被隔离丢弃 - 若项目已用
React.hydrateRoot启用流式 SSR,styled-components的renderToPipeableStream支持仍不稳定,BEM + CSS Modules 更可靠
CSS Modules + BEM 是 React 18 下最稳的组合
文件名、类名、JSX 访问键三者必须严格对齐,否则 styles.button__icon 必为 undefined。这不是 bug,是 CSS Modules 的设计机制:它按文件名生成哈希前缀,Button.module.css → Button_button__xxx,而 button.module.css 或 MyButton.module.css 会直接断裂映射。
- 文件名必须 PascalCase,且与组件名完全一致(
Card.tsx↔Card.module.css) - CSS 中所有类名必须以
.card开头(禁止.ui-card或.product-card) - Element 必须双下划线:
.card__header✅,.card-header❌(后者不参与 BEM 映射) - JSX 中必须用字符串键访问:
styles['card__header']✅,styles.card__header❌(点号非法)
动态样式需求强时,别硬套 BEM,改用 CSS 自定义属性
如果组件大量依赖 props 控制颜色、尺寸、圆角等,手写 .button--size-large + .button--color-primary 组合会爆炸式增长。这时 styled-components 的函数式写法确实简洁,但代价是环境约束变高。
更轻量的替代方案是:CSS Modules 中声明自定义属性,再用内联 style 注入值:
/* Button.module.css */
.button {
background-color: var(--bg, #007bff);
padding: var(--pad, 0.5rem 1rem);
border-radius: var(--radius, 4px);
}
然后在组件中:
const style = {
'--bg': isPrimary ? '#007bff' : '#6c757d',
'--pad': size === 'large' ? '0.75rem 1.5rem' : '0.5rem 1rem'
};
return <button classname="{styles.button}" style="{style}">...</button>;
- 保留 CSS Modules 的作用域隔离能力
- 规避
styled-components的运行时注入风险 - 支持伪类、媒体查询、动画等完整 CSS 特性(
styled-components中需额外封装) - TS 类型安全:
style对象可被CSSProperties约束
postcss-bem-linter 不是可选插件,是边界守门员
没有它,团队很快会写出 .button__icon--large--dark(Element 下挂 Modifier)、.button .icon(带空格选择器破坏 Block 边界)、甚至跨文件引用 .modal__close 这类反模式。这些错误不会报错,但会悄悄污染语义结构,让协作成本陡增。
它必须集成进构建流程,在编译阶段强制校验:
- 禁止 Modifier 嵌套:
.button--primary--large❌ - 禁止 Element 名含连字符:
.button-icon❌,必须.button__icon✅ - 禁止在
Button.module.css中出现.header__title(非本 Block 类名) - 自动拦截
.button:hover写法(应写成.button:hover本身合法,但若混入.button__content:hover则需确认是否真属该 Block)
真正容易被忽略的是:BEM 不是命名习惯,而是组件边界的显式声明;一旦允许随意跨 Block 引用类名,CSS Modules 的作用域就形同虚设。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











