前言:
“模块化”是指解决一个复杂问题时自顶向下逐层把软件系统划分成若干模块的过程。每个模块完成一个特定的子功能,所有的模块按某种方法组装起来,成为一个整体,完成整个系统所要求的功能。模块具有以下几种基本属性:接口、功能、逻辑、状态,功能、状态与接口反映模块的外部特性,逻辑反映它的内部特性。在软件的体系结构中,模块是可组合、分解和更换的单元。
这里我们对css 的模块化进行介绍:
CSS 发展:
css发展的几个阶段:
- 手写源生 CSS
- 使用预处理器 Sass/Less
- 使用后处理器 PostCSS
- 使用 css modules
- 使用 css in js
手写源生 CSS:
- 行内样式,即直接在 html 中的 style 属性里编写 css 代码。
- 内嵌样式,即在 html 中的 style 标签内编写 class,提供给当前页面使用。
- 导入样式,即在内联样式中 通过 @import 方法,导入其他样式,提供给当前页面使用。
- 外部样式,即使用 html 中的 link 标签,加载样式,提供给当前页面使用。
行内样式的缺点:
- 样式不能复用。
- 样式权重太高,难以重定义覆盖样式。
- 表现层与结构层没有分离,结构混乱,阅读性差。
- 不能进行缓存,影响加载效率。
导入样式(@import)的缺点:
- 导入样式,只能放在 style 标签的第一行,放其他行则会无效。
- @import 声明的样式表不能充分利用浏览器并发请求资源的行为,其加载行为往往会延后触发或被其他资源加载挂起。
- 由于 @import 样式表的延后加载,可能会导致页面样式闪烁。
预处理器 Sass/Less/Scss:
源生的 css 不支持变量,不支持嵌套,不支持父选择器等等,这些种种问题,催生出了像 sass/less/scss 这样的预处理器。
预处理器主要是强化了 css 的语法,对开发者来说比较友好,结构也比较清晰简介,编写方便,提高工作效率,但本质上,打包出来的结果和源生的 css 都是一样的。
后处理器 PostCSS:
随着前端工程化的不断发展,越来越多的工具被前端大佬们开发出来,愿景是把所有的重复性的工作都交给机器去做,在 css 领域就产生了 postcss。
postcss 可以称作为 css 界的 babel,它的实现原理是通过 ast 去分析我们的 css 代码,然后将分析的结果进行处理,从而衍生出了许多种处理 css 的使用场景。
常用的 postcss 使用场景有:
- 配合 stylelint 校验 css 语法
- 自动增加浏览器前缀 autoprefixer
- 编译 css next 的语法
css modules:
CSS Modules 有所不同。它不是将 CSS 改造成编程语言,而是功能很单纯,只加入了局部作用域和模块依赖,这恰恰是网页组件最急需的功能。
CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效。产生局部作用域的唯一方法,就是使用一个独一无二的class的名字,不会与其他选择器重名。这就是 CSS Modules 的做法。
css in js:
自从React出现之后,由于组件结构,强制要求把 HTML、CSS、JavaScript 写在一起,这有利于组件的隔离。每个组件包含了所有需要用到的代码,不依赖外部,组件之间没有耦合,很方便复用。所以,随着 React 的走红和组件模式深入人心,这种"关注点混合"的新写法逐渐成为主流。
CSS 模块化定义:
为解决以往css编写出现以下问题,CSS 模块化应运而生:
- class 命名难以定义。
- class 命名重复覆盖。
- 层级结构不清晰。
- 代码难以复用,冗余。
- common.css多而杂,代码庞大。
CSS 模块化的实现方式:
BEM 命名规范:
BEM 的意思就是块(block)、元素(element)、修饰符(modifier)。是由 Yandex 团队提出的一种前端命名方法论。这种巧妙的命名方法让你的 css 类对其他开发者来说更加透明而且更有意义。
/* 块即是通常所说的 Web 应用开发中的组件或模块。每个块在逻辑上和功能上都是相互独立的。 */
.block {
}
/* 元素是块中的组成部分。元素不能离开块来使用。BEM 不推荐在元素中嵌套其他元素。 */
.block__element {
}
/* 修饰符用来定义块或元素的外观和行为。同样的块在应用不同的修饰符之后,会有不同的外观 */
.block--modifier {
}
通过 BEM 的命名方式,可以让我们的 css 代码层次结构清晰,通过严格的命名也可以解决命名冲突的问题。
CSS Modules:
CSS Modules 指的是我们像 import js文件 一样去引入我们的 css 代码,代码中的每一个类名都是引入对象的一个属性,通过这种方式,即可在使用时明确指定所引用的 css 样式,并且 CSS Modules 在打包的时候会自动将类名转换成 hash 值,完全杜绝 css 类名冲突的问题。
- 定义 css 文件。
.title {
color: red;
}
- 在 js 模块中导入 css 文件。
import style from './App.css';
element.innerHTML = '<div class="' + styles.title + '">';
3.CSS Modules 不能直接使用,而是需要进行打包,CSS Modules 提供各种插件,支持不同的构建工具。这里使用的是 webpack 的css-loader插件,因为它对 CSS Modules 的支持最好。
// webpack.config.js
module.exports = {
module: {
rules: [
{
test: /\.css$/,
use:{
loader: 'css-loader',
options: {
modules: {
// 自定义 hash 名称
localIdentName: '[path][name]__[local]--[hash:base64:5]',
}
}
}
]
}
};
- 打包出来的 css 类名就是由一长串 hash 值生成。
._3zyde4l1yATCOkgn-DBWEL {
color: red;
}
CSS In JS:
CSS in JS,意思就是使用 js 语言写 css,完全不需要些单独的 css 文件,所有的 css 代码全部放在组件内部,以实现 css 的模块化。
实现了CSS-in-JS的库有很多,据统计现在已经超过了61种。虽然每个库解决的问题都差不多,可是它们的实现方法和语法却大相径庭。从实现方法上区分大体分为两种:唯一CSS选择器和内联样式(Unique Selector VS Inline Styles)。
接下来我们会分别看一下对应于这两种实现方式的两个比较有代表性的实现:styled-components和radium。
CSS-in-JS Playground是一个可以快速尝试不同CSS-in-JS实现的网站,可以亲自去体验各种css in js插件。
styled-components:
通过styled-components,你可以使用ES6的标签模板字符串语法(Tagged Templates)为需要styled的Component定义一系列CSS属性,当该组件的JS代码被解析执行的时候,styled-components会动态生成一个CSS选择器,并把对应的CSS样式通过style标签的形式插入到head标签里面。动态生成的CSS选择器会有一小段哈希值来保证全局唯一性来避免样式发生冲突。
styled-components不需要你为需要设置样式的DOM节点设置一个样式名,使用完标签模板字符串定义后你会得到一个styled好的Component,直接在JSX中使用这个Component就可以了。
可以看出styled的Component样式存在于style标签内,而且选择器名字是一串随机的哈希字符串,这样其实实现了局部CSS作用域的效果(scoping styles),各个组件的样式不会发生冲突。除了styled-components,采用唯一CSS选择器做法的实现还有:jss,emotion,glamorous等。
radium:
Radium和styled-components的最大区别是它生成的是标签内联样式(inline styles)。由于标签内联样式在处理诸如media query以及:hover,:focus,:active等和浏览器状态相关的样式的时候非常不方便,所以radium为这些样式封装了一些标准的接口以及抽象。
radium定义样式的语法和styled-components有很大的区别,它要求你使用style属性为DOM添加相应的样式。
radium会直接在标签内生成内联样式。内联样式相比于CSS选择器的方法有以下的优点: 自带局部样式作用域的效果,无需额外的操作 内联样式的权重(specificity)是最高的,可以避免权重冲突的烦恼。
其他区别:
不同的CSS-in-JS实现除了生成的CSS样式和编写语法有所区别外,它们实现的功能也不尽相同,除了一些最基本的诸如CSS局部作用域的功能:
- 自动生成浏览器引擎前缀 - built-in vendor prefix
- 支持抽取独立的CSS样式表 - extract css file
- 自带支持动画 - built-in support for animations
- 伪类 - pseudo classes
- 媒体查询 - media query
- 其他
CSS-in-JS的优点:
- 局部样式 - Scoping Styles
- 避免无用的CSS样式堆积 - Dead Code Elimination
- 用户需要在首屏看到的(above the fold)页面要用到的最少CSS - Critical CSS
- 基于状态的样式定义 - State-based styling
- 封装得更好的组件库
CSS-in-JS的缺点:
- 陡峭的学习曲线 - Steep learning curve
- 运行时消耗 - Runtime cost
- 代码可读性差 - Unreadable class names
- 没有统一的业界标准 - No interoperability
总结:
- CSS Modules不是将CSS改造的具有编程能力,而是加入了局部作用域、依赖管理,这恰恰解决了最大的痛点。可以有效避免全局污染和样式冲突,能最大化地结合现有 CSS 生态和 JS 模块化能力。
- CSS-in-JS有好处也有坏处,我们一定要根据自己的实际情况进行衡量和取舍来确定是不是要在自己的项目中使用它。永远不要为了使用一个技术而用一个技术。
参考:
CSS Modules 用法教程
CSS in JS 简介
CSS in JS的好与坏