先记住这个答案
to() 和 :scope 是 @scope 规则里两个完全不同的东西。to() 设置作用域的下界,让花括号里的样式不穿透到指定元素及其子树。:scope 是个伪类选择器,指代作用域根元素本身,让你可以在 @scope 里给根元素加样式。一个控制范围的「底部在哪里」,一个指向范围的「起点是谁」。
to(.boundary)排除边界及后代:scope指代作用域根元素本身- 下界元素自身也被排除,不只是它的子代
- 多个
to()选择器用逗号分隔
用 to() 防止样式穿透到嵌套组件
组件嵌套是 to() 最典型的使用场景。比如一个 .tabs 组件里面嵌套了另一个 .tabs,外层的 .tabs 样式默认会穿透到内层。
<style>
@scope (.tabs) to (.tabs) {
.tab { border-bottom: 2px solid blue; }
.panel { padding: 16px; }
}
</style>
<div class="tabs">
<div class="tab">外层选项</div>
<div class="panel">
<div class="tabs">
<div class="tab">内层选项 —— 不受外层样式影响</div>
</div>
</div>
</div>@scope (.tabs) to (.tabs) 的意思是:从第一个 .tabs 开始匹配,遇到下一个 .tabs 就停止。外层的 .tab 和 .panel 会被选中并加样式,内层 .tabs 里的 .tab 不会。下界元素(内层 .tabs)本身也被排除。
如果没有 to() 下界,外层 @scope (.tabs) 的样式会一直匹配到所有嵌套层级的 .tab 和 .panel,导致内层组件的样式被污染。
用 :scope 给作用域根自身加样式
@scope 里写的普通选择器只能匹配根的子代元素,选不到根本身。想给根加样式,就用 :scope 伪类。
@scope (.card) {
:scope {
border: 1px solid #e0e0e0;
border-radius: 8px;
overflow: hidden;
}
.title { font-size: 18px; }
.body { padding: 16px; }
}:scope 就是 .card 自己。如果不用 :scope,只写 .card { ... } 也能选到,但这会匹配所有嵌套的 .card,而 :scope 只指代当前作用域根那一个元素。
:scope 的选择器优先级是 (0,1,0),和一个普通类选择器相同。
下界的几个细节
to() 里可以写多个选择器,用逗号分隔。比如 @scope (.card) to (.card, .modal) 会在遇到 .card 或 .modal 时都停止。
下界选择器是相对于作用域根解析的,不是相对于文档根。所以 to(.inner) 只匹配根子树里的 .inner,页面其他地方的 .inner 不受影响。
如果下界选择器匹配到了多个元素,每个都会成为一个隔离边界。从根到任意一个下界元素之间的路径上的元素才在作用域内。
容易答错的地方
- 把
to()和:scope的功能搞反 to()管的是范围的下限,决定样式「到哪里停」。:scope指代作用域根元素本身,用于在选择器里引用「范围从哪里开始」。两者名字都和 scope 相关,但一个是作用域边界的一部分,一个是选择器的一部分。- 以为下界元素还在作用域范围内
- 下界元素自身及其所有子代都被排除在作用域之外。写
@scope (.card) to (.footer)时,.footer和.footer里面的所有元素都不受@scope里的样式影响。
面试官还会怎么问?
不在 @scope 里能用 :scope 吗?
可以。在普通的样式表里,:scope 等同于 :root,指向文档的根元素。在 querySelector 的参数里,:scope 指代调用 querySelector 的那个元素。但这些场景下它和 @scope 没有关系。
如果 to() 选择器匹配不到任何元素会怎样?
作用域就没有下界,等同于没写 to()。@scope 里的样式会匹配根的所有子代元素。这不会报错,只是下界没有生效。
@scope 的下界能动态变化吗?
能。如果通过 JavaScript 动态添加或移除了匹配 to() 选择器的元素,作用域范围会自动更新。新出现的下界元素及其子树会被排除出去,移除的下界元素对应的子树会重新纳入作用域。浏览器会根据 DOM 变化实时重新计算。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。