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 屬性的做法不一樣,這些普通標籤在轉換後還是最初的樣子。