Vue:scoped與module的使用與利弊

clipboard.png

一個web應用是離不開html、css與js,其中css充斥的整個web項目中。css它有一個特定,它是全局的。這樣的特性導致的結果是,一旦你在不同的地方定義了相同的css命名,那麼它們的樣式就會相互覆蓋,最終導致的style錯亂,從而影響整個網頁佈局。

我相信對於每一個前端開發者都遇到過這種css樣式覆蓋的情況,值得慶幸的是,這些問題前輩都已經給出瞭解決方案。

在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>

clipboard.png

最終這屏幕上展示的是兩行紅色的文字,這就是父組件與子組件都定義了title-wrap的樣式,導致子組件的樣式被父組件所覆蓋。

遇到這種情況,可以在style標籤中添加scoped屬性

<style lang="scss" scoped>
.content {
  .title-wrap {
    font-size: 20px;
    color: green;
  }
}
</style>

clipboard.png

scoped作用的阻止上層的css樣式傳遞到下層,限制當前css作用域,使其只對當前組件生效。

知道了它的作用,下面我們在開深入看下它的實現。

clipboard.png

clipboard.png

前面的是沒有添加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: green;
  }
}
</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>

clipboard.png

clipboard.png

由於父組件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;
}

clipboard.png

通過 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>

clipboard.png

module的用法也很簡單,只要在style中增加module屬性即可。不同之處是它在佈局中的引用,都需要添加前綴$style。因爲通過module作用的style都被保存到$style對象中。我可以通過console查看它的具體引用名。

mounted() {
  console.log(this.$style)
  console.log(this.$style['title-wrap'])
}

clipboard.png

通過觀察,發現引用名有一定的規律。都是已index開頭,後面再接着style中定義的命名,最後再接個後綴。這裏的index是父組件的文件名index.vue。所以通過module作用的style將會重新命名爲:文件名_原style名_不定後綴。

這麼命名又有什麼好處呢?我們再來看下展示的效果

clipboard.png

當我們在瀏覽的控制檯查看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>

clipboard.png

clipboard.png

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>

clipboard.png

更多module相關操作可以點擊查看

總結

scoped與module都非常簡單、易用,那麼又該如何選擇呢?

通過上面的使用對比,發現scoped不需要額外的知識,只要在style中定義scoped屬性即可,使用非常簡便。但它的侷限性是適用於中小項目中。因爲scoped作用的style對於我們來說不直觀,對於快速查找定位,module更加合適,同時module對於style向下傳遞的控制權也非常靈活;額外的還有變量導出等便捷功能。

所以如果你是小項目中且低成本的使用,scoped更加適合;而對大項目module更加合適,雖然有一點學習成本,但對於用更好的控制權、可觀性與定位速度來說也就不值一提。

公衆號

感覺不錯的可以來一波關注,掃描下方二維碼,關注公衆號:怪談時間,及時獲取最新知識技巧與互聯網新動態。

clipboard.png

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章