坚持BEM因它解决CSS-in-JS无法处理的语义混乱与协作对齐问题,支持可枚举修饰符、TypeScript联动、零运行时开销及清晰的命名结构。

为什么不用 CSS-in-JS 也要坚持 BEM
因为 CSS-in-JS 解决不了语义混乱和协作对齐问题。比如 styled.div 生成的类名是哈希值,emotion 或 styled-components 的动态样式在多人 Review 时根本看不出结构意图;而 BEM 的 button__icon--large 一眼就能判断这是“按钮里的图标元素,带 large 修饰”,连设计师都能参与命名评审。它不依赖运行时,不增加 bundle 体积,且与 TypeScript 接口定义天然互补——ButtonProps 里声明 size?: 'small' | 'large',对应到 class 就是 button--small 和 button--large,类型和样式语义完全同步。
React 中用 cn 函数生成 BEM 类名最稳妥
直接拼字符串或用模板字面量写 className={`button button--${size} ${disabled ? 'button--disabled' : ''}` 容易漏空格、错 modifier、难维护。推荐用 @bem-react/classname 的 cn 工厂函数:
import { cn } from '@bem-react/classname';
const b = cn('button');
// 基础块
b(); // 'button'
// 元素
b('icon'); // 'button__icon'
// 修饰符(支持对象/数组)
b({ size: 'large', disabled: true }); // 'button button--large button--disabled'
// 组合:块 + 元素 + 修饰符
b('icon', { theme: 'primary' }); // 'button__icon button__icon--theme-primary'
- 避免手写重复前缀,减少 typo 错误
- modifier 为
false或undefined时自动忽略,不用三元判断 - 支持嵌套调用,适合封装高阶组件逻辑(如
b('label', b('text'))) - 与 TypeScript 联动良好:
cn('button')('icon')返回string,可直接用于className
SCSS + BEM 的目录与文件组织必须扁平化
别把所有 BEM 样式塞进一个 components.scss。每个 React 组件配一个同名 SCSS 文件(如 Button.tsx 对应 Button.module.scss),并在其中严格遵循层级:
DMap GL地图JSAPI(dmapgl)开发指南。用于编写、审查、调试 DMap 地图 API 代码,涵盖地图初始化、覆盖物渲染、图层管理、事件处理、控件交互等任务。当用户提及 dmap、dmapgl、jsapi‑gl 或相关地图开发需求时自动触发。
.button {
// Block 样式
&__icon {
// Element 样式
}
&--large {
// Modifier 样式
}
&__icon--hidden {
// Element Modifier —— 注意不是 .button--large__icon
}
}
- 禁止跨 block 使用嵌套(如
.modal .button),否则破坏封装性 - Modifier 必须作用于 block 或 element,不能写成
.button--large__icon—— 这违反 BEM 语义,也导致无法被cn正确识别 - 如果要用全局工具类(如
mt-8),单独放在utilities.scss,不在 BEM 文件里混用 - Vite 或 Webpack 需开启
css.modules模式,确保类名局部作用域,避免意外泄漏
BEM 修饰符命名必须可枚举、可类型化
修饰符不是自由发挥的字符串,而是受控的有限集合。比如 button--variant-primary 比 button--blue 更合理,因为前者能映射到 TypeScript 枚举或联合类型:
立即学习“前端免费学习笔记(深入)”;
type ButtonVariant = 'primary' | 'secondary' | 'outline';
type ButtonSize = 'sm' | 'md' | 'lg';
interface ButtonProps {
variant?: ButtonVariant;
size?: ButtonSize;
}
- 避免用状态描述修饰符,如
--is-loading—— 加载是瞬时状态,应由 JS 控制 class 切换,而非作为静态修饰符存在 - 禁止用媒体查询命名修饰符(如
--mobile-only),响应式应通过@media规则内部处理 - 当 modifier 值来自 props 且可能为空时,
cn('button')({ variant })会自动跳过 undefined 值,比手动条件拼接更安全
真正容易被忽略的是修饰符的“语义边界”:它只表达外观变体或行为变体(如 --disabled),不表达数据状态(如 --has-error 应该由父组件传入 error prop 并控制子元素显隐,而不是靠修饰符撑起整个错误 UI)。

















