一、核心规则
规则 :在同一个 <style scoped>
块中,所有选择器都会使用 相同的 data-v-id ,且该 id 会被添加到所有 非穿透部分 的选择器节点上。
二、标准处理流程
原始选择器:
css
.p .a .b:hover > .c
转换过程:
- 生成唯一 id:
data-v-111
- 遍历选择器 AST
- 在每个 有效节点 后插入属性选择器
转换结果:
css
.p[data-v-111] .a[data-v-111] .b[data-v-111]:hover > .c[data-v-111]
三、技术细节剖析
1. 同一性原理
- 组件级唯一 :每个 Vue 组件在编译时会生成唯一的
data-v-*
哈希值 - 全局唯一性:通过文件内容哈希确保不同组件的 id 绝不重复
- 源码实现:
js
// vue-loader 生成哈希的简化代码
const hash = require('hash-sum')
const id = hash(fs.readFileSync(filePath)) // 基于文件内容生成哈希
2. 选择器处理规则
选择器类型 | 处理方式 | 示例转换 |
---|---|---|
标签选择器 | 添加属性选择器 | div → div[data-v-111] |
类选择器 | 添加属性选择器 | .cls → .cls[data-v-111] |
伪类/伪元素 | 不中断属性插入 | :hover → [data-v-111]:hover |
组合符 | 跳过处理,但两侧元素仍被处理 | > 保持原样 |
穿透选择器内部 | 禁用属性插入 | :deep(.x) → .x |
3. AST 转换示例
原始 AST:
js
Selector [
Class(.p),
Combinator( ),
Class(.a),
Combinator( ),
Class(.b),
Pseudo(:hover),
Combinator(>),
Class(.c)
]
转换后 AST:
js
Selector [
Class(.p),
Attribute([data-v-111]),
Combinator( ),
Class(.a),
Attribute([data-v-111]),
Combinator( ),
Class(.b),
Attribute([data-v-111]),
Pseudo(:hover),
Combinator(>),
Class(.c),
Attribute([data-v-111])
]
四、边界情况验证
1. 伪元素处理
原始代码:
css
.input::placeholder
转换结果:
css
.input[data-v-111]::placeholder
2. 属性选择器共存
原始代码:
css
button[disabled].primary
转换结果:
css
button[disabled][data-v-111].primary[data-v-111]
3. 复杂媒体查询
原始代码:
css
@media (max-width: 768px) {
.container > .item { ... }
}
转换结果:
css
@media (max-width: 768px) {
.container[data-v-111] > .item[data-v-111] { ... }
}
五、与穿透选择器的对比
1. 无穿透选择器
原始代码:
css
.parent .child .item
转换结果:
css
.parent[data-v-111] .child[data-v-111] .item[data-v-111]
2. 使用穿透选择器
原始代码:
css
.parent :deep(.child) .item
转换结果:
css
.parent[data-v-111] .child .item[data-v-111]
3. 穿透组合符
原始代码:
css
:deep(ul > li)
转换结果:
css
ul > li /* 无任何 data-v 属性添加 */
六、工程实践意义
-
样式隔离保障:
- 通过统一 data-v-id 实现组件级样式隔离
- 避免不同组件间的样式污染
-
性能优化:
- 单次哈希计算重复利用
- AST 操作时间复杂度 O(n)
-
可维护性:
- 开发时写普通 CSS
- 构建时自动转换实现作用域