全文共 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,选择了哪个,就用哪个的体系去管理项目就行了。