原文:Vue.js - Scoped Styles vs CSS Modules
做者:Michał Sajnóg 發表時間:Aug 28, 2018
譯者:西樓聽雨 發表時間: 2018/9/10
(轉載請註明出處)css
譯者按:本文講解的主要是 Scoped 樣式和 CSS Module 的對比,對這兩個概念已經熟悉的同窗,一樣也建議看一下,由於文中還提到了一些如 CSS Modules 的 「:export」概念,及 Scoped 樣式存在一些缺陷,若是你對這些細節也已經熟知,那麼請儘快離開這個頁面,以避免浪費您時間。html
The most commonly used solution that we use to reduce the main pain points is introducing the BEM (Block Element Modifier) methodology. However, it addresses only a small part of the bigger problem.vue
在現代化的 Web 開發中,CSS 還遠未完美,這一點應該沒有什麼意外。現今的項目一般都至關複雜,而 css 樣式天生又是全局性的,因此到最後老是極容易地就發生樣式衝突——要麼是樣式相互覆蓋,要麼就是隱式地級聯到了下面那些咱們未考慮到的元素。webpack
在減輕 CSS 存在的主要痛點方面,咱們廣泛採用的解決方案是引入 BEM (Block Element Modifier) 方法學。不過這隻能解決咱們這個大問題的很小一部分。ios
Every new Vue.js application created by vue-cli comes with two great built-in solutions: Scoped CSS and CSS Modules. Both of them have some pros and cons, so let’s take a closer look and see which solution might be a better fit for your case.git
咱們很是幸運,社區已經開發出了一些解決方案,他們能夠幫咱們處理這些問題。說不定你已經據說過了 CSS Modules、Styled Components、Glamorous、JSS——這些只是衆多流行的工具中的少數幾個。若是你對這個話題感興趣,你能夠查看這篇帖文——做者 Indrek Lasn 對 CSS-in-JS 的思想作了很是詳盡的講解。github
每一個經過 vue-cli 建立的 Vue.js 應用都內置了兩個很好的解決方案:Scoped CSS 和 CSS Modules (模塊式 CSS)。兩種方案各有優缺點,因此下面咱們就仔細看下哪一種方案在你的案例中更適用。web
In order to get scoped styles working, we just have to add a scoped attribute to the **<style>**
tag:vue-cli
咱們只須要在 <style>
標籤上添加一個 scoped 屬性便可啓用 scoped 樣式:安全
<template>
<button class=」button」 />
</template>
<style scoped> .button { color: red; } </style>
複製代碼
It will apply our styles only to elements in the same component by using PostCSS and transforming the above example to the following:
這樣就會使得咱們的樣式只被應用到這個組件中的元素上。這是藉助 PostCSS 實現的,它會將上面的代碼轉換成下面這樣:
<style> .button[data-v-f61kqi1] { color: red; } </style>
<button class=」button」 data-v-f61kqi1></button>
複製代碼
As you can see, it requires no effort at all to have nicely scoped styles, and it also handles scoping tags’ styles in the same way.
就像你看到的這樣,整個過程不須要作什麼就能夠達到很好的 scoped 樣式效果。
Now, if you need to - let’s say - change the width of a component in a specific view, you can apply an extra class to it and style it as you normally would with all the benefits of scoped styles:
如今假設你須要調整一個視圖中的某個組件的寬度,那麼你能夠像你平時那樣作的同樣:在這個組件上添加一個額外的 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>
複製代碼
However, be aware that this feature was introduced with one drawback - if your child component's root element has a class that also exists in the parent component, the parent component's styles will leak to the child. You can check out this CodeSandbox to get a better understanding of the problem.
Although it is not recommended and should be avoided - there are cases where we need to style something deeply inside our child component. For the sake of simplicity, let’s assume that our parent component should be responsible for the styling header of the BasePanel component. In scoped styles, the >>>
combinator (also known as /deep/
) comes in at this point.
此次仍是同樣,不須要作什麼你就得到了對佈局的完全控制。
不過請注意:這個特性存在一個缺陷,即若是你子組件的根元素上有一個類已經在這個父組件中定義過了,那麼這個父組件的樣式就會泄露到子組件中。若是想更好地理解這個問題,能夠查看這個 CodeSandbox 例子。
還有一些狀況是咱們須要對咱們的子組件的深層結構設置樣式——雖然這種作法並不受推薦且應該避免。爲了簡便起見,咱們假設咱們的父組件如今要對 BasePanel 的標題設置樣式,在 scoped 樣式中,這種狀況可使用 >>>
鏈接符(或者 /deep/
)實現。
<style scoped> .pricing-panel >>> .title { font-size: 24px; } </style>
複製代碼
經轉換後:
.pricing-panel[data-v-b52c41] .title {
font-size: 24px;
}
複製代碼
Plain and simple, huh? But be aware that we just lost the encapsulation. Any .title
class that will be used inside this component (even implicitly by a grandchild) will be affected by these styles.
很是簡單,是吧?但是別忘記,咱們卻所以失去了組件的封裝效果。這個組件內的全部的 .title
類的樣式都會被這些樣式所浸染——即使是孫節點。
CSS Modules gained their popularity due to the React community that quickly adopted this technology. Vue.js takes it to another level by combining its power with simplicity of use and out-of-the-box support by using vue-cli.
模塊式 CSS 的流行源於 React 社區,它得到了社區的迅速的採用。Vue.js 更甚之,其強大、簡便的特性在加上經過 vue-cli 對其開箱即用的支持,將其發展到另外一個高度。
Now let’s look at how we can use it:
如今讓咱們來看下怎麼使用它:
<style module> .button { color: red } </style>
複製代碼
What makes it so special and different from scoped styles is that all the created classes are accessible via the $style
object inside the component. So in order to apply this class we have to use class binding:
此次咱們使用的不是 scoped
屬性,而是 module
。這等於告訴 vue-template-compiler 和 vue-cli 的 webpack 配置要對這一部分採用哪些相應的 loader,進而生成像下面這樣的 CSS:
.ComponentName__button__2Kxy {
color: red;
}
複製代碼
What makes it so special and different from scoped styles is that all the created classes are accessible via the $style
object inside the component. So in order to apply this class we have to use class binding:
它的特殊之處以及和 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>
複製代碼
The first benefit is that by looking at this element in our HTML we immediately know which component it belongs to. Secondly, everything becomes very explicit and we have full control - no magic whatsoever. However, we have to be careful while styling HTML tags, as they land in the final CSS as-is, as opposed to scoped styles, where even plain tags are scoped by the unique data attribute.
Similar to the second example from scoped styles, let’s see how we can style the component in a certain context:
它的第一點好處就是,當咱們在 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>
複製代碼
It simply gets the job done, without any surprises! Moreover, because all classes are available through the $style
object we can now pass them however deep we want using props, making it super easy to use a class in any place of the child component:
毫無心外,跟咱們指望的結果同樣。此外,由於全部的 CSS 類能夠經過 $style
對象獲取到,因此咱們能夠經過 props 將這些類傳遞到任何咱們但願的深度中,這樣,在子組件中的任意位置使用這些類就會變得極其容易:
<template>
<BasePanel title="Lorem ipsum" :titleClass="$style.title" >
Content
</BasePanel>
</template>
複製代碼
CSS Modules have great interoperability with JS, and they do not limit you to classes. Using :export
keyword, we can also export additional things to our $style
object.
模塊式 CSS 與 JS 有着很好的互操做性 (interoperability),這一點不僅侷限於 CSS 類。咱們還可使用 :export
關鍵字將其餘的東西導出到 $style
對象上。
Imagine you have a chart to develop - you can keep your colour variables in CSS, and additionally export them for use in your component:
例如,想象一下你有一個圖表須要開發 —— 你能夠在 CSS 中定義你的色彩變量的同時將其導出,以供你的組件使用:
<template>
<div>{{ $style.primaryColor }}</div> <!-- #B4DC47 -->
</template>
<style module lang="scss"> $primary-color: #B4DC47; :export { primaryColor: $primary-color } </style>
複製代碼
I only scratched the surface here - the CSS Modules concept is much broader and I encourage you to check out the full specification to know more.
對於模塊式 CSS的概念,我這裏還只是講到了它的皮毛,它實際要寬泛的多,建議你查看下它完整的規範以瞭解更多。
Both solutions are very simple, easy to use and, to an extent, solve the same issue. Which one should you choose then?
Scoped styles require literally no extra knowledge to use and feel comfortable with. Their limitations also make them simple to use, and they're capable of supporting small to mid-sized applications.
However, when it comes to more complex scenarios and bigger apps, we probably want to be more explicit and have more control over what’s going on in our CSS. Even though using the $style
object multiple times in a template might not look so sexy, it’s a small price to pay for the safety and flexibility it allows. We also get easy access to our variables (like colours or breakpoints) in JS, without having to keep separate files in sync.
Which one do you use? And why? Feel free to share any additional scenarios you encountered along the way!
其實兩種方案都很是簡單、易用,在某種程度上解決的是一樣的問題。 那麼你該選擇哪一種呢?
scoped 樣式的使用不須要額外的知識,給人溫馨的感受。它所存在的侷限,也正是它的使用簡單的緣由。它能夠用於支持小型到中型的應用。
在更大的應用或更復雜的場景中,這個時候,對於 CSS 的運用,咱們就會但願它更加顯式,擁有更多的控制權。雖然在模板中大量使用 $style
看起來並不那麼「性感」,但卻更加安全和靈活,爲此咱們只需付出微小的代價。還有一個好處就是咱們能夠用 JS 獲取到咱們定義的一些變量(如色彩值、樣式斷點),這樣咱們就無需手動保持其在多個文件中同步。