严格模式下全局CSS重复挂载,是因为在组件内import或useEffect动态插入link导致effect双执行;正确做法是仅在入口文件顶层单次import './global.css'。

CSS Modules 本身不处理全局样式,严格模式下直接 import './global.css' 是安全的,但必须确保它只执行一次且不依赖组件生命周期。
为什么严格模式下全局 CSS 会重复挂载
React 严格模式会故意双调用函数组件(包括模块顶层代码),但 import 语句本身是模块级单次执行——真正出问题的是那些「在组件内部 import」或「通过 useEffect 动态插入 」的写法。常见错误现象:document.querySelectorAll('link[href*="global.css"]').length === 2,浏览器开发者工具里看到两条重复的 标签。
- 不要在组件函数体内写
import './global.css'—— 这会导致每次渲染都触发模块加载(实际不会重复执行,但易误导) - 避免用
useEffect(() => { const link = document.createElement('link'); ... }, [])注入样式:严格模式下该 effect 会执行两次,哪怕加了空依赖数组 - 第三方库(如
normalize.css或antd/dist/antd.css)必须在入口文件(index.js或main.jsx)顶部一次性导入,不能分散到组件中
import './global.css' 必须放在模块顶层,且仅限一次
这是最简单也最可靠的方案。Webpack/Vite/Rollup 都会把顶层 import 编译为单次注入逻辑,与 React 渲染无关。
- 正确位置:在
src/index.js、src/main.jsx或自定义的styles/global.css入口里统一引入 - 错误位置:任何
.jsx组件文件内部(哪怕只是import语句,也不该出现在组件作用域里) - 如果使用 CSS 预处理器(如 Sass),确保
@import也只出现在全局入口的主文件中,而非被多个组件分别@import
想局部启用全局规则?用 :global() 而不是脱离 CSS Modules
当某个 .module.css 文件需要穿透作用域(比如覆盖第三方库样式、重置某些标签默认行为),应该用 :global() 语法,而不是另建一个普通 .css 文件再 import —— 后者容易失控。
- 支持写法:
:global(.ant-btn) { border-radius: 0; },只影响这个模块文件内声明的全局选择器 - 禁止写法:
.ant-btn { border-radius: 0; }(没包:global()),这会被 CSS Modules 哈希化成类似.Button_ant-btn__xyz789,完全失效 - 注意嵌套:Sass/Less 中
:global不支持嵌套写法,必须扁平展开,例如:global(.modal .header) { padding: 0; }
服务端渲染(SSR)时 className 不匹配警告的根源
这个警告(Prop `className` did not match)往往不是因为全局 CSS 重复,而是因为你把本该全局生效的样式逻辑,错误地塞进了 CSS Modules 流程里——比如给 normalize.css 加了 .module.css 后缀,或者用 import styles from './normalize.module.css' 方式加载。
- 检查所有非组件专属的 CSS 文件:它们的后缀必须是
.css,绝不能是.module.css - Vite 用户需确认没有在
vite.config.js中对.css文件误配css.modules选项 - Webpack 用户要核对
css-loader配置:只有.module.css应匹配modules: { mode: 'local' };普通.css应走modules: false分支
真正危险的不是「引入全局样式」,而是「把全局样式当成局部模块来管理」——一旦混淆边界,构建工具和运行时就无法区分哪些该哈希、哪些该透出,后续的 hydration、热更新、甚至打包体积都会出隐性问题。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











