::part() 并非万能,需组件显式声明 part 属性、exportparts 导出,且浏览器支持;大小写敏感、不支持动态绑定与 slot 投影;微前端需逐层导出;firefox 不支持,降级须用 css 变量;不可与 ::slotted() 混用,part 名称属公开 api。

::part() 不是“穿透 Shadow DOM 的万能钥匙”,它只在组件作者显式暴露、且浏览器支持的前提下才生效。直接写 my-button::part(icon) 却没反应?大概率是这三件事漏了:内部没加 part 属性、没用 exportparts 透出、或目标浏览器根本不认这个语法。
为什么 ::part() 写了却完全不生效
根本原因不是选择器错了,而是浏览器压根没找到匹配节点——::part(label) 只会查找 Shadow DOM 内部**真实存在 part="label" 属性的元素**,大小写敏感、静态声明、不支持 slot 投影内容。
- 检查 DevTools 中 shadow root 里是否存在带
part="label"的节点(不是class="label",也不是data-part) -
part="Label"和::part(label)不匹配;part="label required"可被::part(required)单独选中 - 宿主元素(如
<my-input></my-input>)自身不能加part,::part()只作用于其 shadow 内部子节点 - 动态绑定无效:
part={this.type}、:attr(part)、v-bind:part全部不触发样式计算
exportparts 是微前端里 ::part() 生效的硬门槛
在微前端场景(如 qiankun 或 MicroApp),子应用若用了 attachShadow({ mode: 'open' }),外部想定制其内部部件,仅靠 <my-card part="title"></my-card> 是不够的——必须由子应用在定义时显式导出:
- 正确写法:
customElements.define('my-card', class extends HTMLElement { … }); my-card.prototype.exportparts = 'title:header-title btn:action-btn'; - 外部 CSS 必须对应写
my-card::part(header-title),而非::part(title) - 未声明
exportparts时,::part()在子应用 shadow 内部完全不可见,主应用无法命中 - 嵌套自定义元素(如
<editor-toolbar></editor-toolbar>在<my-editor></my-editor>内部)需逐层exportparts,不能靠父级“自动透传”
兼容性现实与降级必须手动做
::part() 在 Firefox 截至 2026 年 7 月仍未实现,@supports selector(::part(foo)) 永远返回 false;Safari 虽从 15.4 支持,但企业内网大量用户仍卡在 15.3 或更早版本。不能依赖 CanIUse 的“支持”标记就上线。
- 真机测试底线:iOS 16.7 + Safari、macOS 12.6 + Safari;低于此版本视为不支持
- 关键样式必须用
@supports selector(::part(foo))包裹,否则旧浏览器会因解析失败丢弃整条规则 - 降级方案只能是 CSS 自定义属性:
my-input { --label-color: #333; }→ shadow 内消费为color: var(--label-color, #333); - 禁用态背景、必填标红等核心视觉逻辑,绝不能只靠
::part()控制,否则 Safari 用户看到的是未定义行为
::part() 和 ::slotted() 别混用,职责完全不同
它们解决的是两类完全不同的样式需求,强行互换只会静默失效:
-
::part(label):只改组件自己 shadow 内渲染的原生节点(比如内部<h2 part="label"></h2>),支持所有 CSS 属性 -
::slotted([slot="icon"]):只影响通过<slot></slot>插入的外部内容(比如使用者传的<svg slot="icon"></svg>),且仅允许 color、font-size 等可继承属性,布局类全部忽略 - 想改插槽内容里的子元素?不行——
::slotted(svg path)无效;得让使用者自己加 class,或组件内部用slot="icon"分发后,在 shadow 内用::part()标记该path -
:host button { cursor: default; }无效,因为外部样式穿不透;唯一合法路径是组件内部给按钮加part="action-btn",再用::part(action-btn)
part 名称一旦发布,就是公开 API。第三方使用者会直接依赖 my-input::part(error),改名等于 breaking change——必须同步文档、版本号和迁移指南,不能只在本地 demo 里试通就交付。前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











