譯者按:本文講解的主要是 Scoped 樣式和 CSS Module 的對比,對這兩個概念已經熟悉的同學,同樣也建議看一下,因為文中還提到了一些如 CSS Modules 的 “:export”概念,及 Scoped 樣式存在一些缺陷,如果你對這些細節也已經熟知,那麼請儘快離開這個頁面,以免浪費您時間。
在現代化的 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> |
經轉換後:
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:
1 2 3 |
.ComponentName__button__2Kxy { color: red; } |
它的特殊之處以及和 scoped 樣式不一樣的地方就在於所有建立的類可以通過這個元件的 $style
物件獲取。因此,要將這個類進行應用,我們需要像下面這樣進行 class 繫結:
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 屬性的做法不一樣,這些普通標籤在轉換後還是最初的樣子。
比較 scoped 樣式中的第二個例子,我們來看下我們可以怎麼對那個元件設定樣式:
1 2 3 4 5 6 7 8 9 10 11 12 |
<template> <BasePanel :class="$style['pricing-panel']"> content </BasePanel> </template> <style module> .pricing-panel { width: 300px; margin-bottom: 30px; } </style> |
其轉換後:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<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 將這些類傳遞到任何我們希望的深度中,這樣,在子元件中的任意位置使用這些類就會變得極其容易:
1 2 3 4 5 6 7 8 |
<template> <BasePanel title="Lorem ipsum" :titleClass="$style.title" > Content </BasePanel> </template> |
模組式 CSS 與 JS 有著很好的互操作性 (interoperability),這一點不只侷限於 CSS 類。我們還可以使用 :export
關鍵字將其他的東西匯出到 $style
物件上。
例如,想象一下你有一個圖表需要開發 —— 你可以在 CSS 中定義你的色彩變數的同時將其匯出,以供你的元件使用:
1 2 3 4 5 6 7 8 9 10 11 |
<template> <div>{{ $style.primaryColor }}</div> <!-- #B4DC47 --> </template> <style module lang="scss"> $primary-color: #B4DC47; :export { primaryColor: $primary-color } </style> |
對於模組式 CSS的概念,我這裡還只是講到了它的皮毛,它實際要寬泛的多,建議你檢視下它完整的規範以瞭解更多。
總結
其實兩種方案都非常簡單、易用,在某種程度上解決的是同樣的問題。 那麼你該選擇哪種呢?
scoped 樣式的使用不需要額外的知識,給人舒適的感覺。它所存在的侷限,也正是它的使用簡單的原因。它可以用於支援小型到中型的應用。
在更大的應用或更復雜的場景中,這個時候,對於 CSS 的運用,我們就會希望它更加顯式,擁有更多的控制權。雖然在模板中大量使用 $style
看起來並不那麼“性感”,但卻更加安全和靈活,為此我們只需付出微小的代價。還有一個好處就是我們可以用 JS 獲取到我們定義的一些變數(如色彩值、樣式斷點),這樣我們就無需手動保持其在多個檔案中同步。