CSS方法论和模块化发展综述

143 阅读10分钟

1. CSS方法论

CSS 方法论可以看做是 CSS 中的设计模式,是一种用来帮助我们进行 CSS 组织管理,命名规范的工程手段,本节内容对几种主流的 CSS 方法论进行简单介绍,对于一些看不太懂的部分,可以查阅本文后面提供的参考资料。

1.1 OOCSS

OOCSS是一种基于面向对象思想的CSS组织方法,于2009年左右由 Nicole Sullivan 提出,其核心理念是抽取公共样式。OOCSS基于两个基本原则:(1) 结构和样式分离 (2) 容器和内容分离

OOCSS可能是我们最熟悉也最容易理解的 CSS 方法论,这跟我们平常写 class 样式的姿势差不多,虽然还是有一些差别。早期 OOCSS 的思想跟前端开发里提倡的 HTML 与 CSS 进行分离是一致的,虽然后面 React 的横空出现使得 HTML 跟 CSS进行强耦合逐渐成为了一种新的主流认知,也带动了 CSS 组件化的发展。

1.2 BEM

BEM (Block, Element, Modifier) 于2009年左右由 Yandex (一家俄罗斯互联网公司)提出,其核心思想是 CSS组件化。

在 BEM 的视角里,样式的编写不再是以整个页面为对象,而是以组件为对象。每个组件就是一个 Block,每个 Block 由一些不同的 Element 组成,然后其不同状态下的样式使用 Modifier 来进行修饰。

BEM 的组件化思想要求你的视角专注在组件内,你不能做超出组件范围之外的事情。比如,你可以设置组件的 padding ,但是你不能设置组件的 margin ,因为这会影响到组件外部。专注组件内部要求提高组件的内聚性,降低与外部组件的耦合性。

BEM 的优点是 组件化后的 CSS 代码可复用,易扩展,易于维护,命名清晰,不容易产生样式优先级冲突和覆盖问题,缺点可能是使用 __-- 看起来命名可能比较丑吧。

1.3 SMACSS

SMACSS (Scalable and Modular Architecture for CSS) 于2011年左右由 Jonathan Snook 提出,其核心思想是样式规则分类,SMACSS将样式规则分为5类:

  • Base:单个元素的基本样式
  • Layout:布局样式
  • Module:模块化部件样式
  • State:模块或布局在不同状态下的样式
  • Theme:主题(风格)样式

与 BEM 专注于组件概念进行对比,SMACSS 的格局看起来要稍大一些,SMACSS 是从整个项目的视角来看待 CSS ,提出一种对整个 CSS 代码库进行规范管理的方法,并且 SMACSS 的 Module 与 State 分类与 BEM 类似,都包含了 CSS 组件化思想。

1.4 ITCSS

ITCSS (Inverted Triangle CSS)于2011年左右由 Harry Roberts 提出,是一个基于 CSS 规则分层设计的 CSS 方法论。这跟 SMACSS 的分类思想相似,但考虑到了 CSS 的层叠性和优先级特性,设计出了一种倒三角架构,从上到下,从宽泛到具体,优先级从低到高,下层规则可以覆盖上层规则,每一层各种规则之间可以按功能进行隔离和扩展。

ITCSS 的倒三角分层为:

  • Settings:这一层进行字体,颜色定义等全局基础设置。
  • Tools:这一层进行全局工具函数设置。
  • Generic:这一层进行 reset/normalize 设置,盒模型类型设置。
  • Elements:在 Generic 层中对浏览器的默认样式进行了消除,这一层设置基本元素的默认样式
  • Ojbects:这一层参考了 OOCSS 的思想,定义一些通用的设计模式,如 OOCSS 中提出的 mdiea object
  • Components:这一层参考 BEM 的组件化思想,以组件为单位进行CSS开发
  • Utilties:这一层可以看做是一个保险抢救措施,只能在这一层使用 !important 规则,可以用来强行覆盖样式规则。

分析 ITCSS 的设计可知,ITCSS 并没有做出颠覆性的创新,而是借鉴和综合了前辈(OOCSS, BEM, SMACSS)的思想,是这三者的综合与改进。

1.5 Atomic

前面提到的四种方法论虽然各有差异,但共同点是朝着CSS组件化发展的。而 Atomic 则走了一条完全相反的路径。

Atomic 的中文意思是原子的,即将原生的CSS属性进行一层封装,提供一个类样式集合,然后用这个集合里的类进行开发。

这样做的好处是

  • 样式文件大小基本固定,不会随着项目的扩张而无限扩张。
  • 当进行新的开发任务时,不需要了解旧的样式是怎么写的,只需专注于功能的实现。

其缺点是:

  • HTML会变得很冗长难看,因为每一个元素标签里都需要写上一堆的类。
  • 学习成本高,开发者需要再记忆一堆原子类,虽然有IDE可以智能提示。

像是最近很流行的 TailwindCSS 就是采用的 Atomic 的设计思想。

2. CSS现阶段的痛点

无论 CSS 方法论怎么进行改进和创新,其目标都是为了

  • 规范化 CSS 开发
  • 减少冗余代码,降低 CSS 代码量
  • 降低 BUG 率
  • 提高 CSS 复用性
  • 提高 CSS 可维护性

而这一切都与 CSS 只有全局作用域,没有局部作用域这个特性息息相关,这导致所有的 CSS 属性都处在同一个命名空间中,并且 CSS 的生效又依赖于层叠规则,即优先级和代码顺序等,导致的后果就是很容易产生冲突和混乱,提高 DEBUG 的难度,特别是对于层叠规则不够熟悉了解的程序员来说。

即便是理论知识丰富的开发者,在对现有代码不熟悉的情况下,为了快速实现新的需求,有时会直接添加新的 CSS 代码去覆盖已有的代码来达成想要的目的,甚至极端情况下会写出很多带有 !important 的样式代码。这样的后果就是CSS越写越多,越写越乱,越写越难维护,最后变成一个混乱不堪的庞然大物。

无论是 OOCSS,BEM,SMACSS ,ITCSS 或是 Atomic,它们都是在CSS现有标准的限制下提出的折中解决方案,而 CSS 的根本痛点要求 CSS 标准推出作用域机制,提供一种模块化机制。

对比 JavaScript 模块化的发展历程,一开始 JavaScript 也是没有模块化系统的,但是 JavaScript 有函数这个天然的局部作用域,从而发展出以函数闭包为基础的模块模式,到后面的 CommonJS, AMD,到最后的ES6 Module。

而 CSS 模块化的发展正处于探索阶段中。

3. CSS模块化发展现状

前端社区为解决 CSS 模块化问题做出了很多探索性尝试,在这里我介绍现有的一些比较知名的解决方案。

3.1 CSS in JS

CSS in JS,顾名思义就是在 JavaScript 中写 CSS,CSS in JS 是随着前端组件化框架如 React,Vue 等的兴起而兴起的,前端组件化的发展天然的产生了如何将 CSS 与组件进行绑定,且不同组件之间的 CSS不会产生相互影响的需求,这个需求诞生了 Component Scope 的概念。

用 JavaScript 的函数作用域进行类比, 函数可以访问外界环境的变量,也可以屏蔽外界变量,且不同函数(没有嵌套关系)之间是访问不了彼此的变量的。

Component Scope 要做的就是同样的事情,但是这个问题真的很难给出一种通用的解决方案,需要做取舍。

比如,从一个组件的角度来说,我希望这个组件是能够复用的,无论我把它用在哪里,它都应该不依赖外界环境而独立地正常地工作,就跟纯函数类似,一个纯函数不会依赖外界,也不会对外界产生影响,它只根据每次传给它的参数而工作。同样地,一个组件也应该只依据传给它的状态数据进行工作。

但是这样不可避免地会产生样式的冗余,像是字体,颜色,一些通用的样式,是完全可以使用全局样式的。那怎么办呢?要完全的独立就会产生代码冗余,要简洁就必须依赖外界环境,所以说这是一个取舍问题。

回到 CSS in JS 本身,CSS in JS 的库真是五花八门,群魔乱舞,各有各的特点,但它们要做的无非就是两件事情:

  1. 怎么在 JavaScript 中写 CSS
  2. 怎么实现 Component Scope

首先来看看 React 本身自带的一种 CSS in JS 方案,React 为元素提供了一个 style 属性,跟原生 HTML 元素的 style 属性类似,style 接收一个对象,在这个对象里我们可以写 CSS。这就是React 原生的 CSS in JS 方案,当然不好用,React 也并不推荐这么用。

React 的这种思路是采用行内样式来实现 Component Scope ,采用这种思路的 CSS in JS 框架有 Radiuminline CSS。但是这种思路的缺陷就是有一些 CSS 效果你是没法使用行内样式来实现的,比如说媒体查询,hover等状态样式等。

const divStyle = {
  color: 'blue',
  backgroundImage: 'url(' + imgUrl + ')',
};

function HelloWorldComponent() {
  return <div style={divStyle}>Hello World!</div>;
}

接着来看看 Vue 是如何实现 CSS in JS 的,其实 Vue 严格意义上并没有实现 CSS in JS 方案,因为在 Vue 里,该怎么写 CSS 还是怎么写 CSS,但是 Vue 提供了一种单文件组件机制,可以把一个组件的 UI 结构,样式和逻辑写在一个文件里面,因此从我个人的角度来看,我觉得这也算是 CSS in JS。

Vue的单文件里提供了 … 区域可以用来写 CSS,然后在构建时会将 CSS 抽取到一个单独的 CSS 文件中。同时 Vue 提供给 提供了一个 scoped 属性,可以用来实现 Component Scope。Vue 是使用哪种方式来实现 Component Scope 的呢?答案是通过属性选择器,下面的例子来自 Vue 官方文档:

<style scoped>
.example {
  color: red;
}
</style>

<template>
  <div class="example">hi</div>
</template>

上面的代码经过 PostCSS 翻译后是这样的:

<style>
.example[data-v-f3f3eg9] {
  color: red;
}
</style>

<template>
  <div class="example" data-v-f3f3eg9>hi</div>
</template>

除了使用行内样式和属性选择器来实现 Component Scope 外,还有一种常见的方法是给每个组件的样式生成独一无二的类名,采用这种方式的 CSS in JSS 库有 JSSEmotionStyled Component 等。

3.2 CSS Modules

CSS Modules 其实解决 CSS 模块化的手段跟 CSS in JSS 中的一些方案是一样的:通过生成独一无二的类名在 CSS 全局作用域中伪造局部作用域。但与 CSS in JS 用 JavaScript 来写 CSS不同的是,CSS Modules 仍然按照原生写 CSS 的方式来写 CSS,但是通过 webpack 等打包工具(害,其实目前也就Webpack 和 PostCSS-Modules 支持)的支持,我们可以直接在组件中引入 CSS 文件对象(这个引入的 CSS 文件本质上是一个映射对象)然后用这个CSS 对象的类名属性来指定组件的样式。

4. 参考资料

4.1 CSS方法论综述性资料

Organizing your CSS - Learn web development | MDN

What is Modular CSS?

Scope in CSS with and without JavaScript

CSS方法论完全总结

4.2 OOCSS资料

Object Oriented CSS

An Introduction To Object Oriented CSS (OOCSS) - Smashing Magazine

4.3 BEM资料

BEM 101 | CSS-Tricks

BEM - Block Element Modifier

Methodology

You Probably Need BEM CSS in Your Life (Tutorial)

4.4 SMACSS资料

Home - Scalable and Modular Architecture for CSS

4.5 ITCSS资料

Harry Roberts - Managing CSS Projects with ITCSS

Managing CSS Projects with ITCSS

ITCSS: Scalable and Maintainable CSS Architecture - Xfive

CSS 架构之 ITCSS - 掘金

4.6 Atomic资料

CSS 架构之 ACSS - 掘金

4.7 CSS in JS

An Introduction to CSS-in-JS: Examples, Pros, and Cons

github.com/MicheleBert…

CSS-in-JS - Wikipedia

React: CSS in JS

4.8 CSS Modules

CSS Modules