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 樣式:

 

 

1

2

3

4

5

6

7

8

9

<template>

  <button class=”button” />

</template>

 

<style scoped>

  .button {

    color: red;

  }

</style>

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

 

 

1

2

3

4

5

6

7

<style>

.button[data-v-f61kqi1] {

  color: red;

}

</style>

 

<button class=”button” data-v-f61kqi1></button>

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

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

 

 

1

2

3

4

5

6

7

8

9

10

11

12

<template>

  <BasePanel class=”pricing-panel”>

    content

  </BasePanel>

</template>

 

<style scoped>

  .pricing-panel {

    width: 300px;

    margin-bottom: 30px;

  }

</style>

經轉換後:

 

 

1

2

3

4

5

6

7

8

9

10

11

12

13

<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/ )實現。

 

 

1

2

3

4

5

<style scoped>

  .pricing-panel >>> .title {

    font-size: 24px;

  }

</style>

經轉換後:

 

JavaScript

1

2

3

.pricing-panel[data-v-b52c41] .title {

  font-size: 24px;

}

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

模組式 CSS

模組式 CSS 的流行源於 React 社群,它獲得了社群的迅速的採用。Vue.js 更甚之,其強大、簡便的特性在加上通過 vue-cli 對其開箱即用的支援,將其發展到另一個高度。

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

 

 

1

2

3

4

5

<style module>

  .button {

    color: red

  }

</style>

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

 

JavaScript

1

2

3

.ComponentName__button__2Kxy {

  color: red;

}

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

 

 

JavaScript

1

2

3

4

5

6

7

8

9

<template>

  <button :class="$style.button" />

</template>

 

<style module>

  .button {

    color: red

  }

</style>

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

 

 

1

2

3

4

5

6

7

<style>

  .ComponentName__button__2Kxy {

    color: red;

  }

</style>

 

<button class=”ComponentName__button__2Kxy”></button>

 

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