Scoped CSS(Vue Loader)
在vue單文件組件中,為了防止全局同css類名名樣式的污染,vue-loade對單文件組件 <style>
標簽增加了scoped
屬性的處理。原理就是在html標簽上添加data-v-xxxxxxxx
屬性,然后在css類名后添加屬性選擇器,即利用css類選擇 + 屬性選擇器實現樣式局部化:
Parent.vue
<template>
<div class="parent">
我是來自父組件的
</div>
</template>
<style lang="scss" scoped>
.parent {
color: #333;
}
</style>
轉換結果:
<template>
<div data-v-2f3286d4 class="parent">
我是來自父組件的
</div>
</template>
<style>
.parent[data-v-2f3286d4] {
color: #333;
}
</style>
使用 scoped
后,父組件的樣式將不會滲透到子組件中。不過一個子組件的根節點會同時受其父組件的 scoped CSS 和子組件的 scoped CSS 的影響。這樣設計是為了讓父組件可以從布局的角度出發,調整其子組件根元素的樣式。
我們將Parent.vue修改為:
Parent.vue
<template>
<div class="parent">
<Child></Child>
我是來自父組件的
</div>
</template>
<script>
import Child from './Child'
export default {
name: 'Parent',
components: {
Child
}
}
</script>
新增Child.vue
<template>
<div class="child">
<div>
我是子組件默認色的
</div>
<div class="red">
我是子組件紅色的
</div>
<!--<div data-v-2f3286d4 class="red">-->
<!--我是子組件被父組件編譯過的紅色的-->
<!--</div>-->
</div>
</template>
<script>
export default {
name: 'Child'
}
</script>
<style lang="scss">
.child {
color: #999;
.red {
color: red;
}
}
</style>
下面看下如何在父組件中修改子組件中樣式(兩種情況):
- 父有
scoped
,子無scoped
,這種情況也是常見的各種ui中的實現,每個ui組件中無scoped,我們在父組件中可以覆蓋每個ui組件的默認樣式。如上邊提到的父組件加scoped后,在子組件的根節點會加入data-v-2f3286d4
,我們在父組件直接這樣寫是沒用的:
<style lang="scss" scoped>
.parent {
color: #333;
.red {
color: greenyellow;
}
}
</style>
效果:
以上直接修改的話,會被編譯為:
<style lang="scss" scoped>
.parent .red[data-v-2f3286d4] {
color: greenyellow;
}
</style>
子組件中red選擇的標簽是沒有 data-v-2f3286d4
屬性的,但是我們可以在子組件中打開注釋測試下:
<div data-v-2f3286d4 class="red">
我是子組件被父組件編譯過的紅色的
</div>
效果如下:
我們需要加 /deep/
或者 >>>
或者 ::v-deep
來修改子組件中 .red
的樣式(會在 deep
使用后的class編譯為 `[data-v-xxxxxxxx] .red形式),我們繼續對之前打開注釋的子組件進行注釋,並修改父組件為:
<style lang="scss" scoped>
.parent {
color: #333;
/deep/ .red {
color: greenyellow;
}
}
</style>
上邊會被編譯為
.parent[data-v-2f3286d4] .red {
color: greenyellow;
}
效果:
- 父有
scoped
,子有scoped
,這種情況下大多出現在我們自己的公共組件中,這種方式並不推薦,我們寫的公共組件應該不含有scoped
。參照1
中提到的穿透組件寫法我們出現的結果如下:
出現這種問題的原因就是屬性選擇器權重 > class選擇器權重,解決的方法就是需要提高父組件中覆蓋樣式的權重,方法很簡單:加 !important
。。。
<style lang="scss" scoped>
.parent {
color: #333;
/deep/ .red {
color: greenyellow !important;
}
}
</style>
效果:
data-v-xxxxxxxx
生成規則
我們看到標簽上新增屬性,可能有些小伙伴會好奇data-v-xxxxxxxx中的xxxxxxxx
是如何生成的,我查閱vue loader的倉庫中搜索發現:是否生產環境 ? hash(組件的內容) : hash(組件的相對路徑):
const moduleId = 'data-v-' + hash(isProduction ? content : shortFilePath)****
后來有大佬發現,只根據內容有可能會是生成hash值相同,比如以下方式聲明組件, issue地址:
<style lang="sass" src="./index.sass" scoped></style>
<script lang="ts" src="./index.ts"></script>
<template lang="pug" src="./index.pug"></template>
后來就改為 內容 + 相對路徑
生成hash, commit地址:
const moduleId = 'data-v-' + hash(isProduction ? (shortFilePath + '\n' + content) : shortFilePath)
scoped
總結
添加了屬性選擇器,對於CSS選擇器的權重加重了
即使加入屬性選擇器,但是clsss還是沒變,如果全局還存在同class名稱的樣式,還是有可能出現覆蓋,比如用在App.vue
中也定義了.red
樣式:
效果:
推薦使用
CSS Modules
,我們直接生成一個唯一的class名,既保證了class的全局唯一(無法造成class名樣式污染),有沒有提高class選擇器權重的增加
參考:
你知道style加scoped屬性的用途和原理嗎?
Scoped CSS
CSS Modules
New scoped ID generation since v13.4 may cause duplicated ID (needs option to disable)