CSS容器查询可通过“仅命名”实现原生作用域隔离,无需尺寸判断;BEM等命名规范是低成本落地方式,而Shadow DOM才是唯一原生样式边界,需显式启用并正确注入样式。

data-* 属性不是样式隔离开关,只是选择器前缀载体
很多人以为给容器加 data-app="user-profile" 就能自动隔离样式,其实它本身不提供任何隔离能力——它只是让 CSS 选择器多了一个可写的限定条件。真正起作用的是你是否把所有相关样式都重写成 div[data-app="user-profile"] .btn 这种形式。
常见错误现象包括:
- 子应用用了 Ant Design,它的
.ant-btn仍全局生效,data-*完全无效 - 父应用写了
[data-theme="dark"] .header,结果子应用里没加前缀的.header被意外染黑 - 第三方库注入的
normalize.css重置了body字体大小,data-*对它毫无约束力
class/id 必须带组件级前缀,BEM 是最低成本落地方式
泛化类名如 button、modal 在多组件共存时必然冲突。BEM 不是“高级技巧”,而是必须执行的命名底线。
实操建议:
立即学习“前端免费学习笔记(深入)”;
- 根元素用唯一 block 名:
class="user-profile",不是class="profile" - 子元素严格双下划线:
class="user-profile__avatar",避免连字符或驼峰 - 状态修饰符用双短横:
class="user-profile--loading",不写is-loading - 禁止在 class 中塞样式描述:
class="big-red-text"随主题变更就失效,class="user-profile__heading"才可持续
Shadow DOM 是唯一原生样式边界,但必须显式启用
customElements.define() 本身不创建 Shadow DOM,只注册标签名。没调用 this.attachShadow({ mode: 'closed' }),所有“封装”都是假象。
关键细节:
- 必须在
constructor()中调用,不能拖到connectedCallback()—— 否则挂载瞬间已暴露在 light DOM 中 -
mode: 'closed'是生产环境唯一推荐值;mode: 'open'等于主动开放沙箱门 - 样式必须手动注入到
shadowRoot:不能写<link rel="stylesheet">到主文档,也不能拼innerHTML带<style> - 动态插入 HTML 字符串时,需用
DOMParser解析,再把<style>提取为CSSStyleSheet实例,通过adoptedStyleSheets注入
框架 scoped CSS 本质是编译时障眼法,无法防穿透
<style scoped> 或 CSS Modules 并非运行时隔离,它们只是构建阶段给 class 打哈希后缀(如 button_f3f2a1b4),再让选择器带上对应属性前缀(如 [data-v-f3f2a1b4])。
这意味着:
- 外部仍可用
button { color: red }或* { margin: 0 }全局规则穿透影响 -
v-html、dangerouslySetInnerHTML插入的内容完全不受保护 - Webpack 中若未开启
css-loader的modules: { mode: 'local' },就等于没开隔离 - SSR 场景下,服务端生成的哈希必须与客户端一致,否则 hydration 失败
真正起效的从来不是 HTML 标签本身,而是你是否在构建、加载、注入、继承这四个环节都做了对齐——比如 BEM 类名要和 JS 查询范围对齐,data-* 要和 PostCSS 前缀规则对齐,Shadow DOM 要和样式注入时机对齐。漏掉任一环,隔离就形同虚设。



















