重构不是重写,而是用BEM给类名打“身份证”;必须优先处理泛化命名、高频混杂类及JS硬编码类,禁用后代选择器与跨block@extend,通过别名过渡、SCSS单层嵌套及工具链校验保障安全迁移。
重构大型项目样式不是重写 CSS,而是用 BEM 给每个类名打上可追溯、可隔离、可协作的“身份证”。盲目全局替换 .btn 为 button__text 会导致 JS 绑定断裂、样式错乱、CI 构建失败——这不是重构,是事故。
先筛出三类高危类名,它们才是重构起点:
.btn、.card、.nav 这类泛化命名:在 JS 中被 document.querySelector('.btn') 硬编码,在 HTML 中随意复用,还常搭配 !important 强行覆盖header.css 或 common.css 中的 .sidebar,git blame 显示多人反复修改,说明它已承载多个业务逻辑document.querySelector(['"].[a-z]+['"]),找出所有被硬编码的类,这些要加别名过渡,不能直接删跳过 .clearfix、.hidden 这类工具类,统一迁到 u-clearfix、u-hidden 前缀下,和 BEM 业务类物理隔离。
老项目里 <div class="form"><input></div> 这种结构,硬拆成 form__input 会牵连 JS 事件绑定和表单校验逻辑。正确做法是原地加归属:
class="form" 改成 class="login-form"
<input> 加 class="login-form__input"
.login-form__input 规则,删掉所有孤立的 .input
.login-form .input 这种后代选择器——它默认你永远不重构父容器,而 BEM 的契约是“删掉 login-form,子元素仍能渲染”如果旧 HTML 里已有 class="btn btn-red",先保留 btn-red 作别名,新代码只用 button--primary,逐步替换。
&__icon 看着省事,但一不小心就写出 &__input &__icon(带空格),编译出来是后代选择器,等于悄悄绕过 BEM 隔离。
.search-form { &__input {} &--compact {} }
&__input { &__icon {} }——这会生成 .search-form__input__icon,违反 BEM 元素不可嵌套原则@extend 禁用跨 block:哪怕 .button--small 和 .input--small 样式相同,也必须各自定义,否则修改一处,两处同时崩$button-padding-vertical,而不是 $padding-sm,避免团队误以为它是全局尺寸标准写 className={`button ${isDisabled ? 'button--disabled' : ''}`} 是最高危破规点。本地开发看着能跑通,但 CI 构建环境对空格、大小写、连字符更敏感,button__icon_error 少个 - 或多一个空格,就变成无效类名,样式失效且无提示。
clsx 或 classnames 库:cn('button', { 'button--disabled': isDisabled })
const BLOCK = 'login-form',然后用 cn(`${BLOCK}__submit`, { [`${BLOCK}__submit--loading`]: loading })
-- 或 __,所有组合必须来自预定义字符串或工具函数真正难的不是写对一个类名,而是让所有人每次敲 class= 时,大脑自动触发 BEM 语义检查——这需要 pre-commit 钩子里的 stylelint、CI 中的 npx stylelint "/*.{css,scss}"、以及 PR 模板里强制填写「本次修改涉及哪些 block 的类名变更」。漏掉任一环,规范就只是文档里的一行字。