CSS模块化管理方案综述
前端发展这么多年来,由于babel,webpack等工程化工具的快速发展,JS的模块化管理能力已经远远强于CSS,另外也有很大一部分原因在于JS本身作为一门编程语言就有自己的作用域,而CSS本身的特性决定了其规则都是全局作用域。但如果要实现项目的工程化,这又是不得不解决的一个难点,下面针对CSS发展以来一些比较流行合理的模块化方案分别做一个思路梳理。
预处理器
CSS难以管理的本质还是这门语言缺少相对应的编程能力,当语言能力不足时,很自然的就想到通过另一门语言开发,然后编译到该底层再运行的选项。这就是预处理器的目的:通过增强的语法赋予CSS更强的编程能力。常用的预处理器有Less,Sass,Stylus,也都是大同小异,关键的能力都是有的。
文件切分
传统的CSS如果要切分文件只能使用原生的@import
指令或者加载多个CSS文件,但这些方法都会增加http请求导致性能问题,预处理器拓展了@import
的能力,它将不同的css从语义上导入,最终将编译成一个CSS文件。这也就给予了我们切分模块管理CSS的能力。
变量,函数,mixin
增强CSS的抽象和复用能力,相当于补全了CSS缺少了通用编程语言功能,让代码更加DRY。
嵌套
可以让一系列CSS规则呈现出类似于HTML的层级关系,使得CSS更加易读。
BEM
除了通过增强CSS本身的能力实现模块化之外,通过语义化约束的方式也是一种方案。BEM就是最为流行的该类方案,简单来说就是通过模块(block)+元素(element)+修饰符(modifier)的命名规范来确保模块的唯一性和可重用性。
block
一个块就是一个组件, 该命名应该具有唯一的意义,无论是语义或是视觉表现上的。比如模态框或者列表等。块的命名应该遵循如下原则:
- 只能使用class
- 每一条规则必须属于一个块
比如一个列表块的CSS命名如下:
.list {
}
element
块中的子元素,类名必须用父级块的名称作为前缀,通常使用双下划线连接。比如上例列表块中的某个列表项元素则可以命名如下:
.list__item {
}
modifier
修饰符则是一个块的特定状态,通常前缀必须为块或者元素,通常用双横线连接。比如一个选中的列表,或者一个蓝色背景的列表项:
.list--selected {
}
.list__item--blue {
}
小结
简单来说BEM作为一种命名约束,当然会存在类名过长,命名繁琐等问题,应该可以归类为轻管理方案,并不和其他模块化方案互斥,可以视情况配合其他模块化方案使用。
CSS Modules
可以看到以上的方案虽然在一定程度上实现了模块化,但并没有在语法上实现真正的封装,CSS作用依然是全局的,还是需要人为的注意class命名的不重复,而这些在工程化项目的的最佳实践理应由工具去解决。CSS Modules就是为此而生的。
原理
CSS Modules根据localIdentName使用混淆算法生成一个原class和混淆class的映射对象来保存CSS和JS的模块依赖关系,并且由于混淆class的独一无二,相当于创建了一个CSS的局部作用域,这样就有了模块化最重要的两个要素:依赖管理和局部作用域。通常配合Webpack配置localdentName构建。
作用域修改
CSS Modules默认会使用:local装饰所有class实现局部作用域,可以通过:global修改为全局。
/* 定义局部 */
.normal {
}
:local(.normal) {
}
/* 定义全局 */
:global(.normal) {
}
样式复用
CSS Modules只提供了composes组合实现样式复用,也支持外部样式复用。实例如下:
.base {
}
.base-compose {
composes: base;
}
外部复用
/* another.css */
.add {
}
/* main.css */
.base {
composes: add from './another.css';
}
变量依赖
CSS Modules的新增伪类:export可以实现变量的输出。
/* another.scss */
$blue: #0c77f8;
:export {
blue: $blue;
}
/* main.js */
import style from './another.scss';
style.blue // '#0c77f8'
总的来说,CSS Modules可以看做是增强版的自动化BEM并且赋予了使用JS管理的能力。
CSS IN JS —— Styled Components
以上方案其实都可以看做是同一类CSS模块化方案——旨在增强CSS本身的能力。而这一类方案则从出发点上就不一样——通过JS完全代理CSS的编写,在这一类方案的代表实现就是React的Styled Components了。
思想
其将React中的组件按样式应用与否分为逻辑组件和展示组件,顾名思义,通过解耦为两类组件,我们可以更专注于JS中样式的编写。使用方法如下:
实例
import React from 'react';
import styled from 'styled-components';
import { render } from 'react-dom';
const SpanView = styled.span`
font-size: 1.5rem;
`;
class App extends React.Component {
render() {
return (
<SpanView>Hello</SpanView>
);
}
}
简单使用就是如此,可以看到我们能直接通过JS变量定义样式,再结合JSX,相当于HTML,CSS,JS全部统一了编写模式并聚合到了一个组件中,算是做到了真正符合直觉的以组件为粒度的模块化。这里就不做具体介绍,更多的使用方法可以查阅官方文档。
对比
相比于第一类方案,优缺点也是显而易见的。
优点:
- 组件内样式全部使用JS管理更加灵活易用。
- 跨平台的良好支持,尤其是本身没有CSS的运行环境。
缺点:
- 不易结合CSS的生态工具,例如postcss等。
- 无法利用CSS本身的特性,比如层叠等。
总结
综上可以看出,没有哪一类模块化管理方案现在是完美的,还是要根据具体项目的特性来决定。
-- EOF --