1. 程式人生 > >vue中scoped樣式與css Module 對比

vue中scoped樣式與css Module 對比

在現代化的 Web 開發中,CSS 還遠未完美,這一點應該沒有什麼意外。

現今的專案通常都相當複雜,而 css 樣式天生又是全域性性的,所以到最後總是極容易地就發生樣式衝突——要麼是樣式相互覆蓋,要麼就是隱式地級聯到了下面那些我們未考慮到的元素。

在減輕 CSS 存在的主要痛點方面,我們普遍採用的解決方案是引入 BEM (Block Element Modifier) 方法學。不過這隻能解決我們這個大問題的很小一部分。

我們非常幸運,社群已經開發出了一些解決方案,他們可以幫我們處理這些問題。說不定你已經聽說過了 CSS Modules、Styled Components、Glamorous、JSS——這些只是眾多流行的工具中的少數幾個。如果你對這個話題感興趣,你可以檢視這篇帖文——作者 Indrek Lasn 對 CSS-in-JS 的思想做了非常詳盡的講解。

每個通過 vue-cli 建立的 Vue.js 應用都內建了兩個很好的解決方案:Scoped CSS 和 CSS Modules (模組式 CSS)。兩種方案各有優缺點,所以下面我們就仔細看下哪種方案在你的案例中更適用。

Scoped 樣式

我們只需要在 style 標籤上新增一個 scoped 屬性即可啟用 scoped 樣式:

<template>
  <button class=”button” />
</template>
<style scoped>
  .button {
    color: red;
  }
</style>

這樣就會使得我們的樣式只被應用到這個元件中的元素上。這是藉助 PostCSS 實現的,它會將上面的程式碼轉換成下面這樣:

<style>
.button[data-v-f61kqi1] {
  color: red;
}
</style>
<button class=”button” data-v-f61kqi1></button>

就像你看到的這樣,整個過程不需要做什麼就可以達到很好的 scoped 樣式效果。

現在假設你需要調整一個檢視中的某個元件的寬度,那麼你可以像你平時那樣做的一樣:在這個元件上新增一個額外的 class 來設定其樣式。

<template>
  <BasePanel class=”pricing-panel”>
    content  </BasePanel>
</template>
<style scoped>
  .pricing-panel {
    width: 300px;
    margin-bottom: 30px;
  }
</style>

經轉換後:

<style>
  .base-panel[data-v-d17eko1] {
    ...
  }
  .pricing-panel[data-v-b52c41] {
    width: 300px;
    margin-bottom: 30px;
  }
</style>
<div class=”base-panel pricing-panel” data-v-d17eko1 data-v-b52c41>
  content</div>

這次還是一樣,不需要做什麼你就獲得了對佈局的徹底控制。

不過請注意:這個特性存在一個缺陷,即如果你子元件的根元素上有一個類已經在這個父元件中定義過了,那麼這個父元件的樣式就會洩露到子元件中。如果想更好地理解這個問題,可以檢視這個 CodeSandbox 例子。

還有一些情況是我們需要對我們的子元件的深層結構設定樣式——雖然這種做法並不受推薦且應該避免。為了簡便起見,我們假設我們的父元件現在要對 BasePanel 的標題設定樣式,在 scoped 樣式中,這種情況可以使用 >>> 連線符(或者 /deep/ )實現。

<style scoped>
  .pricing-panel >>> .title {
    font-size: 24px;
  }
</style>

經轉換後:

.pricing-panel[data-v-b52c41] .title {
  font-size: 24px;
}

非常簡單,是吧?可是別忘記,我們卻因此失去了元件的封裝效果。這個元件內的所有的 .title 類的樣式都會被這些樣式所浸染——即便是孫節點。

模組式 CSS

模組式 CSS 的流行源於 React 社群,它獲得了社群的迅速的採用。

Vue.js 更甚之,其強大、簡便的特性在加上通過 vue-cli 對其開箱即用的支援,將其發展到另一個高度。

現在讓我們來看下怎麼使用它:

<style module>
  .button {
    color: red
  }
</style>

這次我們使用的不是 scoped 屬性,而是 module。這等於告訴 vue-template-compiler 和 vue-cli 的 webpack 配置要對這一部分採用哪些相應的 loader,進而生成像下面這樣的 CSS:

.ComponentName__button__2Kxy {
  color: red;
}

它的特殊之處以及和 scoped 樣式不一樣的地方就在於所有建立的類可以通過這個元件的 $style 物件獲取。因此,要將這個類進行應用,我們需要像下面這樣進行 class 繫結:

<template>
  <button :class="$style.button" />
</template>
<style module>
  .button {
    color: red
  }
</style>

這段程式碼將生成下面的 HTML 及相關的樣式:

<style>
  .ComponentName__button__2Kxy {
    color: red;
  }
</style>
<button class=”ComponentName__button__2Kxy”></button>

它的第一點好處就是,當我們在 HMTL 中檢視這個元素時我們可以立刻知道它所屬的是哪個元件;第二點好處是,一切都變成顯式的了,我們擁有了徹底的控制權——不會再有什麼奇怪的現象了。和 scoped 樣式那種把普通的標籤也加上那些 data 屬性的做法不一樣,這些普通標籤在轉換後還是最初的樣子。

比較 scoped 樣式中的第二個例子,我們來看下我們可以怎麼對那個元件設定樣式:

<template>
  <BasePanel :class="$style['pricing-panel']">
    content  </BasePanel>
</template>
<style module>
  .pricing-panel {
    width: 300px;
    margin-bottom: 30px;
  }
</style>

其轉換後:

<style>
  .BasePanel__d17eko1 {
    /* some styles */
  }
  .ComponentName__pricing-panel__a81Kj {
    width: 300px;
    margin-bottom: 30px;
  }
</style>
<div class="BasePanel__d17eko1 ComponentName__pricing-panel__a81Kj">
  content</div>

毫無意外,跟我們期望的結果一樣。此外,因為所有的 CSS 類可以通過 $style 物件獲取到,所以我們可以通過 props 將這些類傳遞到任何我們希望的深度中,這樣,在子元件中的任意位置使用這些類就會變得極其容易:

<template>
  <BasePanel
    title="Lorem ipsum"
    :titleClass="$style.title"
  >
    Content  </BasePanel>
</template>

模組式 CSS 與 JS 有著很好的互操作性 (interoperability),這一點不只侷限於 CSS 類。我們還可以使用 :export 關鍵字將其他的東西匯出到 $style 物件上。

例如,想象一下你有一個圖表需要開發 —— 你可以在 CSS 中定義你的色彩變數的同時將其匯出,以供你的元件使用:

<template>
  <div>{{ $style.primaryColor }}</div> <!-- #B4DC47 -->
</template>
<style module lang="scss">
  $primary-color: #B4DC47;

  :export {
    primaryColor: $primary-color
  }
</style>

對於模組式 CSS的概念,我這裡還只是講到了它的皮毛,它實際要寬泛的多,建議你檢視下它完整的規範以瞭解更多。

總結

其實兩種方案都非常簡單、易用,在某種程度上解決的是同樣的問題。 那麼你該選擇哪種呢?

scoped 樣式的使用不需要額外的知識,給人舒適的感覺。它所存在的侷限,也正是它的使用簡單的原因。它可以用於支援小型到中型的應用。

在更大的應用或更復雜的場景中,這個時候,對於 CSS 的運用,我們就會希望它更加顯式,擁有更多的控制權。雖然在模板中大量使用 $style 看起來並不那麼“性感”,但卻更加安全和靈活,為此我們只需付出微小的代價。還有一個好處就是我們可以用 JS 獲取到我們定義的一些變數(如色彩值、樣式斷點),這樣我們就無需手動保持其在多個檔案中同步。