全文共 4000 餘字,預計花費 30 分鐘。
衆所周知,CSS 根據選擇器名稱去全局匹配元素,它沒有做用域可言,好比你在頁面的兩個不一樣的地方使用了一個相同的類名,先定義的樣式就會被覆蓋掉。CSS 一直缺少模塊化的概念,命名衝突的問題會持續困擾着你。每次定義選擇器名稱時,總會顧及其餘文件中是否也使用了相同的命名,這種影響在組件開發中尤其明顯。💣💣💣css
理想的狀態下,咱們開發一個組件的過程當中,應該能夠隨意的爲其中元素進行命名,只須要保證其語義性便可,而沒必要擔憂它是否與組件以外的樣式發生衝突。html
與 JavaScript 社區中的 AMD、CMD、CommonJS、ES Modules 等相似,CSS 社區也誕生了相應的模塊化解決方案:BEM、OOCSS、SMACSS、ITCSS,以及 CSS Modules 和 CSS-in-JS 等。前端
根據這些 CSS 模塊化方案的特色,我簡單的將它們分爲了三大類:vue
爲了不 CSS 選擇器命名衝突的問題,以及更好的實現 CSS 模塊化,CSS 社區在早期誕生了一些 CSS 命名方法論,如 BEM、OOCSS、SMACSS、ITCSS、SUITCSS、Atomic CSS 等。node
它們幾乎都有一個共同的特色——爲選擇器增長冗長的前綴或後綴,並試圖經過人工的方式來生成全局惟一的命名。這無疑會增長了類命名的複雜度和維護成本,也讓 HTML 標籤顯得臃腫。react
BEM(Block Element Modifier)是一種典型的 CSS 命名方法論,由 Yandex 團隊(至關於中國的百度)在 2009 年前提出,它的核心思想是 經過組件名的惟一性來保證選擇器的惟一性,從而保證樣式不會污染到組件外。webpack
BEM 命名規約是 .block-name__element-name--modifier-name
,即 .模塊名__元素名--修飾器名
三個部分,用雙下劃線 __
來明確區分模塊名和元素名,用雙橫線 --
來明確區分元素名和修飾器名。你也能夠在保留 BEM 核心思想的前提下,自定義命名風格,如駝峯法、使用單下劃線、使用單橫線等。git
在 BEM 中不建議使用子代選擇器,由於每個類名已經都是全局惟一的了,除非是 block 相互嵌套的場景。github
<!-- 示例模塊 --> <div class="card"> <div class="card__head"> <ul class="card__menu"> <li class="card__menu-item">menu item 1</li> <li class="card__menu-item">menu item 2</li> <li class="card__menu-item card__menu-item--active">menu item 3</li> <li class="card__menu-item card__menu-item--disable">menu item 4</li> </ul> </div> <div class="card__body"></div> <div class="card__foot"></div> </div>
.card {} .card__head {} .card__menu {} .card__menu-item {} .card__menu-item--active {} .card__menu-item--disable {} .card__body {} .card__foot {}
使用 Sass/Less/Stylus 的父元素選擇器 &
能夠更高效的編寫 BEM:web
.card { &__head {} &__menu { &-item { &--active {} &--disable {} } } &__body {} &__foot {} }
OOCSS(Object-Oriented CSS)即面向對象的 CSS,它借鑑了 OOP(面向對象編程)的抽象思惟,主張將元素的樣式抽象成多個獨立的小型樣式類,來提升樣式的靈活性和可重用性。
OOCSS 有兩個基本原則:
好比:咱們有一個容器是頁面的 1/4 寬,有一個藍色的背景,1px 灰色的邊框,10px 的左右邊距,5px 的上邊距,10px 的下邊距。之前對於這樣一個樣式,咱們經常給這個容器建立一個類,並把這些樣式寫在一塊兒。像下面這樣。
<div class="box"></div> <style> .box { width: 25%; margin: 5px 10px 10px; background: blue; border: 1px solid #ccc; } </style>
然而使用 OOCSS 的話,咱們不能這樣作,OOCSS 要求爲這個容器建立更多的「原子類」,而且每一個樣式對應一個類,這樣是爲了後面能夠重複使用這些組件的樣式,避免重複寫相同的樣式,就拿這個實例來講,咱們給這個容器增長下面的類:
<div class="size1of4 bgBlue solidGray mt-5 ml-10 mr-10 mb-10"></div> <style> .size1of4 { width: 25%; } .bgBlue { background: blue; } .solidGray { border: 1px solid #ccc; } .mt-5 { margin-top: 5px; } .mr-10 { margin-right: 10px } .mb-10 { margin-bottom: 10px; } .ml-10 { margin-left: 10px; } </style>
OOCSS 最大的優勢是讓樣式可複用性最大化,也可以顯著減小總體的 CSS 代碼數量。缺點也很明顯,你須要爲每一個元素蒐集一大堆類名,這但是一個不小的體力活 😅。
在 OOCSS 中,類名既要能傳遞對象的用途,也要有通用性,例如 mod、complex、pop 等。若是將 CSS 類命名的太語義化,例如 navigation-bar,那麼就會將其限制在導航欄,沒法應用到網頁的其它位置。
SMACSS(Scalable and Modular Architecture for CSS)便可伸縮及模塊化的 CSS 結構,由 Jonathan Snook 在 2011 年雅虎時提出。
SAMCSS 按照部件的功能特性,將其劃分爲五大類:
SMACSS 推薦使用前綴來區分不一樣部件:
l-
或 layout-
,例如 .l-table
、.layout-grid
等。m-
或模塊自身的命名,例如 .m-nav
、.card
、.field
等。is-
,例如 .is-active
、.is-current
等。theme-
,例如 .theme-light
、.theme-dark
等。<form class="layout-grid"> <div class="field"> <input type="search" id="searchbox" /> <span class="msg is-error">There is an error!</span> </div> </form>
ITCSS(Inverted Triangle CSS,倒三角 CSS)是一套方便擴展和管理的 CSS 體系架構,它兼容 BEM、OOCSS、SMACSS 等 CSS 命名方法論。ITCSS 使用 分層 的思想來管理你的樣式文件,相似服務端開發中的 MVC 分層設計。
ITCSS 將 CSS 的樣式規則劃分紅如下的幾個層次:
!important
。ITCSS 的分層邏輯越往下就越具體,越侷限在某個具體的場景。
根據 ITCSS 的思想,你能夠這樣組織你的 CSS 樣式文件:
stylesheets/ ├── settings/ │ ├── colors.scss │ ├── z-layers.scss │ └── breakpoints.scss ├── tools/ │ ├── mixins.scss │ └── functions.scss ├── generic/ │ ├── box-sizing.scss │ └── normalize.scss ├── base/ │ ├── img.scss │ └── list.scss ├── objects/ │ ├── grid.scss │ └── media.scss ├── components/ │ ├── buttons.scss │ └── slider.scss ├── trumps/ │ ├── widths.scss │ └── gaps.scss └── index.scss
下面是幾個基於 ITCSS 的模版項目,可供參考:
📚 上面提到的這些 CSS 命名方法論,雖然已經不適用於當今的自動化工做流和大前端環境,可是他們有其誕生的時代背景,也確實推進了 CSS 模塊化的發展,其背後的設計思想一樣值得咱們學習,甚至有時候咱們仍然能在某些場合下看到他們的影子。
手寫命名前綴後綴的方式讓開發者苦不堪言,因而 CSS Modules 這種真正的模塊化工具就誕生了。
CSS Modules 容許咱們像 import 一個 JS Module 同樣去 import 一個 CSS Module。每個 CSS 文件都是一個獨立的模塊,每個類名都是該模塊所導出對象的一個屬性。經過這種方式,即可在使用時明確指定所引用的 CSS 樣式。而且,CSS Modules 在打包時會自動將 id 和 class 混淆成全局惟一的 hash 值,從而避免發生命名衝突問題。
這裏僅羅列一些 CSS Modules 的核心特性,更具體的用法能夠參考 官網 或 阮老師的《CSS Modules 用法教程》。
CSS Modules 特性:
:local
中的名稱也屬於本地做用域,定義在 :global
中的名稱屬於全局做用域,全局名稱不會被編譯成哈希字符串。styles.className
。styles['class-name']
,容許但不提倡。🤪composes
屬性來繼承另外一個選擇器的樣式,這與 Sass 的 @extend
規則相似。@value
來定義變量,不過須要安裝 PostCSS 和 postcss-modules-values 插件。/* style.css */ :global(.card) { padding: 20px; } .article { background-color: #fff; } .title { font-size: 18px; }
// App.js import React from 'react' import styles from './style.css' export default function App() { return ( <article className={styles.article}> <h2 className={styles.title}>Hello World</h2> <div className="card">Lorem ipsum dolor sit amet.</div> </article> ) }
編譯結果:
<style> .card { padding: 20px; } .style__article--ht21N { background-color: #fff; } .style__title--3JCJR { font-size: 18px; } </style> <article class="style__article--ht21N"> <h2 class="style__title--3JCJR">Hello World</h2> <div class="card">Lorem ipsum dolor sit amet.</div> </article>
在 webpack 中使用 CSS Modules(開啓 css-loader 的 modules 特性):
// webpack.config.js -> module.rules { test: /\.(c|sa|sc)ss$/i, exclude: /node_modules/, use: [ 'style-loader', { loader: 'css-loader', options: { importLoaders: 2, // 開啓 CSS Modules modules: true, // 藉助 CSS Modules,能夠很方便地自動生成 BEM 風格的命名 localIdentName: '[path][name]__[local]--[hash:base64:5]', }, }, 'postcss-loader', 'sass-loader', ], },
在 PostCSS 中使用 CSS Modules(使用 postcss-modules 插件):
// postcss.config.js module.exports = { plugins: { 'postcss-modules': { generateScopedName: '[path][name]__[local]--[hash:base64:5]', }, }, }
使用 CSS Modules 時,推薦配合 CSS 預處理器(Sass/Less/Stylus)一塊兒使用。
CSS 預處理器提供了許多有用的功能,如嵌套、變量、mixins、functions 等,同時也讓定義本地名稱或全局名稱變得容易。
:global(.title) { color: yellow; } :global { .global-class-name { color: green; } }
在 VSCode 中寫 CSS Modules 代碼,默認是沒有自動提示和跳轉至定義處的功能,不夠智能。
能夠安裝 CSS Modules 擴展。
React 的出現,打破了之前「關注點分離」的網頁開發原則,因其採用組件結構,而組件又強制要求將 HTML、CSS 和 JS 代碼寫在一塊兒。表面上看是技術的倒退,實際上並非。
React 是在 JS 中實現了對 HTML 和 CSS 的封裝,賦予了 HTML 和 CSS 全新的「編程能力」。對於 HTML,衍生了 JSX 這種 JS 的語法擴展,你能夠將其理解爲 HTML-in-JS;對於 CSS,衍生出一系列的第三方庫,用來增強在 JS 中操做 CSS 的能力,它們被稱爲 CSS-in-JS。
隨着 React 的流行以及組件化開發模式的深刻人心,這種"關注點混合"的新寫法逐漸成爲主流。
Any application that can be written in JavaScript, will eventually be written in JavaScript. —— Jeff Atwood
CSS-in-JS 庫目前已有幾十種實現,你能夠在 CSS in JS Playground 上快速嘗試不一樣的實現。下面列舉一些流行的 CSS-in-JS 庫:
styled-components 是目前最流行的 CSS-in-JS 庫,在 React 中被普遍使用。
它使用 ES6 提供的模版字符串功能來構造「樣式組件」。
// styles.js import styled, { css } from 'styled-components' // 建立一個名爲 Wrapper 的樣式組件 (一個 section 標籤, 並帶有一些樣式) export const Wrapper = styled.section` padding: 10px; background: deepskyblue; ` // 建立一個名爲 Title 的樣式組件 (一個 h1 標籤, 並帶有一些樣式) export const Title = styled.h1` font-size: 20px; text-align: center; ` // 建立一個名爲 Button 的樣式組件 (一個 button 標籤, 並帶有一些樣式, 還接收一個 primary 參數) export const Button = styled.button` padding: 10px 20px; color: #333; background: transparent; border-radius: 4px; ${(props) => props.primary && css` color: #fff; background: blue; `} `
// App.js import React from 'react' import { Wrapper, Title, Button } from './styles' // 而後,像使用其餘 React 組件同樣使用這些樣式組件 export default function App() { return ( <Wrapper> <Title>Hello World, this is my first styled component!</Title> <Button>Normal Button</Button> <Button primary>Primary Button</Button> </Wrapper> ) }
更多使用技巧(更具體的內容請參考 官方文檔):
props
),這在須要動態生成樣式規則時特別有用。styled()
來繼承另外一個組件的樣式。createGlobalStyle
來建立全局 CSS 規則。方式一:使用 Scoped CSS(推薦)
爲 <style>
區塊添加 scoped
屬性便可開啓「組件樣式做用域(Scoped CSS)」。
在背後,Vue 會爲該組件內全部的元素都加上一個全局惟一的屬性選擇器,形如 [data-v-5298c6bf]
,這樣在組件內的 CSS 就只會做用於當前組件中的元素。
<template> <header class="header">header</header> </template> <style scoped> .header { background-color: green; } </style>
編譯結果:
<header class="header" data-v-5298c6bf>header</header> <style> .header[data-v-5298c6bf] { background-color: green; } </style>
方式二:使用 CSS Modules
爲 <style>
區塊添加 module
屬性便可開啓 CSS Modules。
在背後,Vue 會爲組件注入一個名爲 $style
的計算屬性,並混淆類名,而後你就能夠在模板中經過一個動態類綁定來使用它了。
<template> <header :class="$style.header">header</header> </template> <style module> .header { background-color: green; } </style>
編譯結果:
<header class="App__header--382G7">header</header> <style> .App__header--382G7 { background-color: green; } </style>
React 並無給咱們提供與 Vue 的 scoped
相似的特性,咱們須要經過其餘方式來實現 CSS 模塊化。
CSS Modules 與 styled-components 是兩種大相徑庭的 CSS 模塊化方案,它們最本質的區別是:前者是在外部管理 CSS,後者是在組件中管理 CSS。二者沒有孰好孰壞,若是你能接受 CSS-in-JS 這種編程模式,更推薦使用 styled-components。若是一時沒法接受,以爲其過於激進了,那就用 CSS Modules。It doesn't matter,選擇了哪個,就用哪個的體系去管理項目就行了。