CSS Modules性能更优,因其构建期生成静态类名与CSS文件,零运行时开销,SSR/微前端/CSP/JS失败场景下稳定可靠;styled-components依赖运行时注入样式,存在解析、计算、注入等可测量开销及不一致风险。

CSS Modules 在绝大多数 React 项目中性能更优,尤其在 SSR、微前端、CSP 严格环境或 JS 加载失败场景下,它几乎没有运行时开销;而 styled-components 的样式注入依赖 JS 执行,存在可测量的 runtime 成本和潜在不一致风险。
构建阶段 vs 运行时样式生成
CSS Modules 的类名哈希和样式映射全部发生在构建期(Webpack/Vite 编译时),输出的是静态 JS 对象 + 普通 .css 文件。浏览器加载后直接使用 styles.button 字符串,无解析、无注入、无重复计算。
styled-components 则必须在运行时执行以下动作:
- 解析模板字符串(
styled.button`...`)并提取 CSS 规则 - 根据 props 动态拼接或计算样式值(哪怕只是布尔判断)
- 生成唯一
sc-xxx类名,并检查是否已存在对应<style>标签 - 将规则注入 DOM(首次渲染可能触发重排)
这些步骤在组件频繁挂载/卸载(如列表滚动、Tab 切换)时会叠加 measurable overhead,尤其在低端设备上。
立即学习“前端免费学习笔记(深入)”;
SSR 和 hydration 不匹配问题
服务端渲染时,CSS Modules 直接输出确定的哈希类名(如 Button_button__abc123),客户端 hydration 完全复用,零 diff。
styled-components 必须手动配置 ServerStyleSheet,漏掉任意一个组件或未包裹 renderToString,就会触发:
审查 React Router 代码,确保数据加载、变更、错误处理和导航模式符合规范,适用于 React Router v6.4+ 代码、加载器及其他特性。
Warning: Prop `className` did not match. Server: "sc-bdVaJa" Client: "sc-fzoLsD"
- 这个 warning 不仅影响日志,还会导致 React 强制丢弃服务端 HTML、重新挂载整个子树(full client-side re-render)
- 即使配置正确,服务端生成的 class 名与客户端首次执行顺序强相关——HMR 或异步 chunk 加载顺序变化都可能破坏一致性
调试与热更新稳定性差异
CSS Modules 的类名在构建产物中固定可见(如 Button_button__mjohd_1),Chrome DevTools 中能直接定位到源文件行号,且支持断点调试样式逻辑(比如条件 className 拼接)。
styled-components 的调试痛点更实际:
- DevTools 中看到的是
sc-fzoLsD这类无意义哈希,需靠babel-plugin-styled-components启用displayName才能勉强识别组件来源 - HMR 热更新时,旧
<style>标签常未被清除,新旧规则共存,优先级混乱(比如 hover 状态残留、媒体查询错位) - jest 测试需额外引入
jest-styled-components才能快照比对生成的样式,否则只测到空对象
真正容易被忽略的点:不是“哪个更快”,而是“失效时谁更稳”
当 CSP 策略禁止 unsafe-inline,styled-components 注入 <style> 会静默失败,页面直接无样式;CSS Modules 的 .css 文件通过 <link> 加载,不受影响。
当 JS bundle 因网络或语法错误中断加载,CSS Modules 已通过 <link> 预加载完成,UI 至少保底可用;styled-components 的样式逻辑则彻底消失。
微前端场景下,多个子应用若用了不同版本的 styled-components,哈希算法不兼容,sc- 类名可能重复,造成样式覆盖——而 CSS Modules 的哈希由构建工具统一控制,天然隔离。


















