Vue中scoped屬性淺析
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還是沒變,根據權重! important
> 屬性選擇器,我們在父元件沒有加scoped時,還是有可能出現覆蓋:
<style lang="scss">
.parent {
color: #333;
.red {
color: greenyellow !important;
}
}
</style>
效果:
推薦使用
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)