關於React的CSS in JS,有一個著名的talk,由Facebook的工程師vjeux帶來。css
裏面最有名的一張slide是這樣的:vue
裏面列舉了CSS的一些問題。其中,Dead Code Elimination,Minification,和Sharing Constants這些問題咱們已經經過在咱們的工做流中加入SASS和PostCSS這樣的CSS預處理器解決了。react
然而還有一些問題沒有解決,好比全局命名空間。同一個document下的全部CSS的類名,都是在同一個「做用域」下的,所以咱們經常要考慮如何避免命名衝突問題。現有的解決辦法主要是靠BEM這樣的命名慣例,或者是用多層CSS父子選擇器來模擬命名空間。然而這樣的辦法對工程師有許多的限制。多級選擇器有比較高的優先級,不容易維護。webpack
Webpack的css-loader首先作出瞭解決全局做用域的嘗試。解決辦法就是在寫CSS類名時加入:local(...)
這樣的標記。git
好比:github
:local(.className) { background: red; } :local .className { color: green; } :local(.className .subClass) { color: green; } :local .className .subClass :global(.global-class-name) { color: blue; }
會被轉化爲:web
._23_aKvs-b8bW2Vg3fwHozO { background: red; } ._23_aKvs-b8bW2Vg3fwHozO { color: green; } ._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 { color: green; } ._23_aKvs-b8bW2Vg3fwHozO ._13LGdX8RMStbBE9w-t0gZ1 .global-class-name { color: blue; }
這裏的辦法就是把CSS類名轉化爲hash字符串,這樣就能夠保證每一個類名都是獨一無二的,天然也就不用在乎命名衝突的問題了。只要在類名在當前模塊內不會相互衝突就能夠了。設計模式
上述的辦法,仍是有一些不便。大多數狀況下,好比在JavaScript中,變量都默認是局部變量。你想要聲明一個全局變量,只能去全局做用域聲明,或者把變量掛到local上(非嚴格模式下,不寫var聲明的是全局變量這種坑就不說了)。sass
Webpack的開發者以後將css-loader中的local變成了默認設定,因而CSS Modules這個規範就呼之欲出了。app
CSS Modules規範。咱們能夠經過css-loader?modules
這個參數來開啓CSS Modules。
CSS Modules中的類名默認就是local的,若是你想要聲明全局類名,能夠加上:global(...)
這個標記。
講CSS Modules的下一個特性以前。咱們先聊點其餘的,咱們知道設計模式中有一條叫作Single Responsibility Principle。
好比咱們有一個button:
.button { display:inline-block; padding:2em; background-color:red; }
與其把這些屬性寫在一個class裏,咱們能夠把它拆分紅多個單獨的class:
.button { display:inline-block; } .button--large{ padding:2em; } .button--warnning{ background-color:red; }
而後在HTML中組合使用就能夠了。
<button class="button button--large button--warnning">
這樣的好處是什麼呢?咱們的UI中,一個組件每每有不少不一樣的狀態。若是咱們將每個class寫成只專一於一個屬性,作好一件事,那就能夠用這些class組合成全部咱們想要的不一樣狀態的組件。相比給每一個狀態的組件寫一個單獨的class,代碼要更優雅簡潔一些。
好比咱們想要一個small尺寸的普通button,只要加兩個class:
.button { display:inline-block; } .button--small{ padding:1em; } .button--large{ padding:2em; } .button--normal{ background-color:blue; } .button--warnning{ background-color:red; }
而後組合就能夠了:
<button class="button button--small button--normal">
要想實現上述的這種組合,可使用SASS的Mixin,但Mixin主要是提供了源代碼中的抽象,最後生成的代碼,和手寫不一樣狀態class的代碼量,是同樣的。
CSS Modules提供的Classes Composing則恰好能夠知足咱們的需求。
好比咱們想渲染一段文字:
.text{ font-size: 20px; composes: red from "./common/color.css"; }
color.css裏是這樣的
.red{ color: red; }
最後渲染出的class是這樣的
<div class="App-text-2AEnE_0 color-red-3ag3h_0"></div>
composes
引入的類被做爲一個單獨的class引入,而不是和text類合在一塊兒。
Vue-loader在v9.8.0以後加入了對CSS Modules的支持。
咱們只要在.vue
文件的<style>
處加一個module
就行
<style lang="sass" module> .text{ font-size: 20px; composes: red from "sass!./common/color.scss"; } </style>
這裏有一點要注意,就是composes
引入的若是是須要預處理器處理的,要在前面加上預處理器的標記,好比SASS用戶就加上sass!
。
若是須要對CSS Modules進行一些配置(其實這個是對Webpack的css-loader的配置,因此配置時能夠參考css-loader的文檔),寫在vue-loader的配置的cssModules
屬性裏便可
loader: 'vue', options: { cssModules: { localIdentName: '[name]-[local]-[hash:base64:5]', camelCase: true } }
vue-loader會自動將一個$style
屬性注入到對應的Vue實例中。在模板中用class binding語法寫就能夠了。
<template> <div :class="$style.app"> <div :class="$style.text"> some text </div> <main-text></main-text> </div> </template>
$style
實際上是一個原class名和處理以後class名的hash,像這樣:
{ app: "App-app-3cl75_0", text: "App-text-2AEnE_0 color-red-3ag3h_0" }
我寫一了一個簡單的DEMO倉庫,能夠供參考。
CSS Modules能夠解決全局做用域和Class組合兩個問題,加上SASS等預處理器,着實讓咱們在寫CSS時的工程化程度大大提升了。
對於使用Vue的同窗來講,vue-loader可使CSS Modules能夠輕鬆的整合到已有的工做流中。若是你正在使用Vue,能夠試試使用CSS Modules。