在Vue中我們通過Scoped與Module來解決。下面我會分別對scoped與module解決方案進行說明,最后在分析它們的利弊與選擇。如果你還未使用過或者說對它們之間的利弊與選擇存在疑問的,相信這篇文章能夠幫你解惑。
Scoped
假設我們有如下一段代碼:
index.vue <template> <div class="content"> <div class="title-wrap">我是紅色的</div> <green-title></green-title> </div> </template> <style lang="scss"> .content { .title-wrap { font-size: 20px; color: red; } } </style>
GreenTitle.vue <template> <div class="content"> <div class="title-wrap">我是綠色的</div> </div> </template> <style lang="scss"> .content { .title-wrap { font-size: 20px; color: green; } } </style>

最終這屏幕上展示的是兩行紅色的文字,這就是父組件與子組件都定義了title-wrap的樣式,導致子組件的樣式被父組件所覆蓋。
遇到這種情況,可以在style標簽中添加scoped屬性
<style lang="scss" scoped> .content { .title-wrap { font-size: 20px; color: red; } } </style>

scoped作用的阻止上層的css樣式傳遞到下層,限制當前css作用域,使其只對當前組件生效。
知道了它的作用,下面我們在開深入看下它的實現。


前面的是沒有添加scoped的源碼,后面是添加了scoped的源碼。我們進行一一對比,發現前面的兩個div標簽都使用了title-wrap樣式,自然導致樣式覆蓋;而后面的兩個div標簽,第一個增加了data-v-67e6b31f的前綴,這就是父組的style中增加scoped的效果,區別與第二個div中的title-wrap樣式。
scoped的實現是借助了PostCSS實現的,一旦增加了scoped,他會將之前覆蓋的樣式轉換成下面的樣式
<style lang="scss"> .content[data-v-67e6b31f] { .title-wrap[data-v-67e6b31f] { font-size: 20px; color: red; } } </style>
通過這種轉換方式,間接的改變了原有的css命名。防止上層組件樣式覆蓋下層組件樣式。
特性
細心的讀者可能會發現上面的后一張源碼圖中第二個div的content中也有data-v-67e6b31f,可能會疑問,第二個content不是子組件中的css嗎?子組件中未添加scoped,為什么還會添加data-v-67e6b31f前綴?
這是scoped的一個特性,使用 scoped 后,父組件的樣式將不會滲透到子組件中。不過一個子組件的根節點會同時受其父組件有作用域的 CSS 和子組件有作用域的 CSS 的影響。這樣設計是為了讓父組件可以從布局的角度出發,調整其子組件根元素的樣式。
所以如果我們將子組件做如下修改
<template> <!-- <div class="content"> --> <div class="title-wrap">我是綠色的</div> <!-- </div> --> </template>


由於父組件scoped特性,所以會影響到子組件的title-wrap,也會添加data-v-67e6b31f前綴
那么又有個疑問,增加了scoped是否就一定不能傳遞的下層組件呢?畢竟我們可能有需要個別樣式傳遞到下層的需求。別急,接着看,這個也能很方便的解決。
深度作用
如果你希望scoped中的某個樣式能夠作用的更深,影響到子組件,你可以使用>>>
操作符
<style scoped> .content >>> .title-wrap { font-size: 20px; color: red; } </style>
注意看我將style中的lang="scss"去掉了,因為加了預處理器后無法正確解析>>>
,這種情況可以使用/deep/代替,本質是>>>
的別名
<style lang="scss" scoped> .content { /deep/ { .title-wrap { font-size: 20px; color: red; } } } </style>
將會編譯成
.content[data-v-67e6b31f] .title-wrap { font-size: 20px; color: red; }

通過 v-html 創建的 DOM 內容不受作用域內的樣式影響,但是你仍然可以通過深度作用選擇器來為他們設置樣式
Module
針對上面的覆蓋問題,還可以通過設置module來解決
<template> <div :class="$style.content"> <div :class="$style['title-wrap']">我是紅色的</div> <green-title></green-title> </div> </template> <style lang="scss" module> .content { .title-wrap { font-size: 20px; color: red; } } </style>

module的用法也很簡單,只要在style中增加module
屬性即可。不同之處是它在布局中的引用,都需要添加前綴$style
。因為通過module作用的style都被保存到$style
對象中。我可以通過console查看它的具體引用名。
mounted() { console.log(this.$style) console.log(this.$style['title-wrap']) }

通過觀察,發現引用名有一定的規律。都是已index開頭,后面再接着style中定義的命名,最后再接個后綴。這里的index是父組件的文件名index.vue。所以通過module作用的style將會重新命名為:文件名原style名不定后綴。
這么命名又有什么好處呢?我們再來看下展示的效果

當我們在瀏覽的控制台查看Elements時,優點顯而易見。相對於scoped的方式,module的方式能夠一眼知道該元素時屬於哪個文件組件中。在大型項目中能夠幫助我們迅速定位到要查找的組件。
除了上述的快速定位,由於module會將所有的style都歸入$style
中,所以我們可以很靈活的將任意的父組件樣式傳遞到任意深層的子組件中。例如,將父組件中的title-wrap
通過props傳遞到子組件中
<template> <div :class="$style.content"> <div :class="$style['title-wrap']">我是紅色的</div> <green-title :styleTitle="$style['title-wrap']"></green-title> </div> </template> <template> <div class="content"> <div :class="styleTitle">我是綠色的</div> </div> </template> <script> export default { props: { styleTitle: String, }, } </script>


module還有一個特性非常不錯,它可以導出定義的變量,將變量歸入$style
中,例如:
<template> <div :class="$style.content"> <div :class="$style['title-wrap']">我是紅色的</div> <green-title :styleTitle="$style['title-wrap']"></green-title> <div>{{$style.titleColor}}</div> </div> </template> <style lang="scss" module> $title-color: red; :export { titleColor: $title-color } .content { .title-wrap { font-size: 20px; color: $title-color; } } </style>

更多module相關操作可以點擊查看
總結
scoped與module都非常簡單、易用,那么又該如何選擇呢?
通過上面的使用對比,發現scoped不需要額外的知識,只要在style中定義scoped屬性即可,使用非常簡便。但它的局限性是適用於中小項目中。因為scoped作用的style對於我們來說不直觀,對於快速查找定位,module更加合適,同時module對於style向下傳遞的控制權也非常靈活;額外的還有變量導出等便捷功能。
所以如果你是小項目中且低成本的使用,scoped更加適合;而對大項目module更加合適,雖然有一點學習成本,但對於用更好的控制權、可觀性與定位速度來說也就不值一提。
作者:午后一小憩
鏈接:https://www.jianshu.com/p/b12941cf96b6
來源:簡書