React中CSS Modules的使用
特别声明:为了感谢社区对W3cplus的支持,在毕业季到来之际,小站开启毕业季优惠,在2019年07月01日至2019年07月10日之间,年费价格为 ¥299.00元。如果您喜欢小站的内容,可以点击开通会员进行全站阅读。如果您对付费阅读有任何建议或想法,欢迎发送邮件至: airenliao@gmail.com!(^_^)
最近项目开始换React的工程,感觉好多东西都得重新开始,特别在撸CSS的时候。说实话和Vue的工程相比,体感差完了。在Vue的工程中除了Modules之外还可以使用CSS的作用域scoped
的概念。用久了Vue的同学,在这方面的感觉爽死了,但是突然切到React的工程体系之下,这方面的感觉突然不要不要。拿个实例来说吧(可能我做得不到位),组件的CSS是全局的,有时覆盖起来,除了蛋疼之外,而费时,费成本。
React项目中写CSS的姿势
比如最简单的一个按钮组件,居然要这么撸:
虽然为每个组件创建一个单一的.scss
文件,并在入口引入相应的样式文件,发现React中的CSS没有域的概念,是全域的。
很多时候需要去覆盖组件初始样式,不得不重新定义样式类或提高选择器权重来处理。着实的蛋疼。除此之外,项目是多人开发,各种各样的类名都有,未统一起来,从覆盖上也增加了不少工作量。另外,还会碰到一些常见的CSS问题,比如:
- 全局污染
- 命名混乱
- 依赖关系复杂
- 无法共享变量
- 代码冗余,难维护
所以最近在重新考虑如何在React项目中编写CSS。以便找到一条更适合自己甚至团队编写、维护CSS的方式:
- 行内样式(在JS中写CSS,最终样式编译到标签元素的
style
内) - CSS-in-JS,较为流行的有
styled-components
、styled-jsx
、react-style
- CSS功能模块(Functional CSS),比如
tailwindcss
、tachyons
等(怎么看都有点类似早期的OOCSS) - CSS Modules
初步对比了React中编写CSS的几种方式,我个人更趋向于CSS Modules,不过在React项目中配置CSS Modules要比在PostCSS或者Vue中配置复杂的多(主要还是自己对Webpack太弱)。这次在配置的时候踩了一些坑,特意梳理一下,以备后用。
我要的目标
习惯了Vue的开发,开始撸React还真不习惯。而我想要的目标是:
React中编写CSS能不能像Vue一样,有作用域的概念,组件的CSS只作用于相应的组件,并不会影响其他组件。
为什么选择CSS Modules
时至今日都在提模块化管理,而前面提到的在React中处理样式的方案都有模块化的身影。而CSS模块化的解决方案主要分为:
- 彻底抛弃CSS,使用JavaScript或JSON来写,比如这两年聊得多的CSS-in-JS。其优点是能给CSS提供JavaScript同样强大的模块化能力;缺点是不能使用CSS处理器以及较难处理伪类选择器的样式
- 依旧使用CSS,但借助JavaScript来管理样式的依赖关系,比如我们今天要聊的CSS Modules。其最大的特点就是最大化的结合了CSS生态和JavaScript模块化能力
不管是哪种CSS模块化,其主要目的是解决:
- CSS样式的导入和导出:灵活的按需导入以便最大化的复用代码;导出时能隐藏内部作用域,以免造成不必要的全局污染
- 解决CSS的编程能力:虽然CSS的处理器(比如Sass、LESS、Stylus和PostCSS)赋予了CSS一些编程能力,但还有有鸡肋之处,它们依旧无法解决模块化最重要的问题
而在React中编写CSS时,有关于这些方面所涉及到的问题表现的更为真切,比如上面提到的:
全局污染
众所周知,CSS是没有作用域名的概念(虽然CSS自定义属性的出现,解决了一些作用域的问题),因此写的样式都是一个全局的。很多时候要去覆盖这些样式,也因此会造成样式可能被错误覆盖。搞不好,你会看到好多样式中会有!important
这样的关键词,甚至更为离谱的是,在行内样式中还会有!important
身影。
另一个更为复杂的是CSS选择器权重,更易于让CSSer犯错,从而也提高了样式代码覆盖的成本。仅管Web Components中的Shadow DOM能彻底解决这个问题,但它的做法有点极端,样式彻底局部化,造成样式重写难度,从而损失了灵活性。
命名混乱
由于全局污染的问题,加上多人协同开发,最易于造成的问题就是样式冲突。为了避免样式冲突,对于元素的命名就有着更高的要求,虽然很多CSS的方法论(比如BEM、OOCSS、Atomic、ITCSS等)可以让我们在编写CSS时尽可能的避免命名的冲突(样式混乱),但并没有彻底解决问题(特别是在一些新生团队),在写CSS的时选择器会非常的复杂(复杂到有超六、七层的嵌套,而最佳的是不超三层),而且命名风格还很难统一。
工程越大,人员越多,命名越乱,选择器越复杂,样式越难覆盖 —— 死循环。
依赖管理不彻底
编写组件最理想的状态 —— 应该相互独立。在引入一个组件时,应该只引入组件自己所需的样式。但现在的做法是除了要引入JavaScript之外,还要引入它的CSS(而CSS处理器以很难做到每个组件编译出单独的CSS)。而在独立的页面中引入所有CSS又会造成不必要的浪费(这也是我不喜欢而没选择Functional CSS原因之一)。
虽然JavaScript模块化已经非常成熟了。比如借助Webpack的css-loader
的能力,可以帮助我们管理CSS依赖。这也是目前较好的方案之一。
无法共享变量
复杂组件要使用JavaScript和CSS来共同处理样式(特别是一些强交互的组件),就会造成有些变量在JavaScript和CSS中冗余。而CSS的处理器是无法提供跨JavaScript和CSS共享变量的这种能力。
值得庆幸的是,CSS的自定义属性可以让我们在JavaScript和CSS共享变量的能力(注意,其实不是变量,是CSS自定义属性)。
代码压缩不彻底
很多压缩工具对于较常的class
类名压缩却无能为力。
事实上,上面提到的这几点都是CSS中一直以来存在的,而又难以解决的。不过借助JavaScript的能力来管理CSS的话,问题就会变得简单的多。这也是CSS-in-JS流行的主要原因,也就出现前面提到的现象,以对象的方式在JavaScript中撸CSS,从而也让不少的同学难以接受这种方式。但CSS Modules的出现,既可以借助JavaScript能力来管理CSS,也方便了CSSer撸代码的习惯,可以说一举两得。这也是为什么选择CSS Modules主要原因。
另外一点,CSS经过这么多年的发展,从SMACSS到OOCSS,再到BEM,可谓是不断的在优化和改进。而CSS Modules与实践单一职责原则的Web组件非常匹配。
我更为好奇的是CSS Modules在设计前端系统中的可能性。CSS Modules基本上是CSS文件,默认情况下类名的作用域名是局部的(本地的)。在任何语言中,全局作用域都被认为是一种不好的实践,而CSS却又是这样的一种模式,只不过这么多年来,大家都无耐的接受了这样的一个现实,也已经学会了在CSS中如何使用它。有了CSS Modules(和其他一些模式),我们就可以跳出CSS的全局作用域,构建模块化系统。
很多同学一直觉得CSS非常简单。事实上,CSS一开始的确很简单,但随着项目的增长,CSS会变得越来越复杂,越来越难以编写和维护,即使是专业的开发人员(CSS大神)也会发现很难在复杂的大型系统中维护和组织样式。(事实上,我也非常的害怕,特别是和一些不太了解CSS的同学一起开发项目)。
CSS Modules的出现,其主要目的就是解决CSS的问题:
通过封装CSS的组件作为闭包,从而遵循组件单一的职责。
如何在工程体系中构建CSS Modules
既然CSS Modules有这么大的优势,那么我们就需要在工程中构建CSS Modules。那么问题来了,如何构建CSS Modules呢?这是当下我们要去解决的问题。
使用Webpack来构建CSS Modules,这也是目前主流方式之一。
而在不同的工程中构建CSS Modules方式是不一样的,比如PostCSS、Vue、React等不同的工程体系中,构建的方式都不一样。除了PostCSS之外借助PostCSS相关插件,其他的工程体系(不管是Vue还是React),都可以借助Webpack来构建。而使用Webpack在React项目中构建CSS Modules又不是一件难事。
对于我这样不太了解Webpack工作机制的人来说,还是一件难事。最起码我觉得比Vue环境下难得了。这个时候我需要一位Webpack高级配置工程师和我一起来构建CSS Modules。
抛开所有JavaScript框架而言,不管是在哪处框架底下,都可以通过Webpack下的css-loader
在配置文件中的加载器选项来启用它。当然,如果你还依赖Sass或PostCSS这样的开发套件的话,那么会相对的增加配置难度。不过,只要一步一步来,一切都不是太大的问题。接下来的内容就来看如何在React的开发环境上构建CSS Modules。
- 如果你是在Vue环境下开发,需要构建CSS Modules的话可以阅读《Vue中的作用域CSS和CSS模块的差异》一文
- 如果你不借助任何JavaScript框架进行开发,但会使用PostCSS来构建开发体系,那么要构建CSS Modules的话可以阅读《PostCSS-modules: 让CSS变得更强大》一文
接下来的内容主要来看如何在React环境下配置CSS Modules以及如何使用CSS Modules。
CSS Modules在create-react-app休系下的的使用
React社区中有一个构建React开发体系的工具Create React App,俗称create-react-app
,有点类似于Vue社区中的Vue-CLI。
在这里不会阐述Create React App如何使用,更多的是会聊聊CSS Modules在Create React App构建的工程体系下如何工作。为了更好的用示例向大家演示CSS Modules的使用,我在Github上创建了一个仓库,在不同的分支下能看到相应的Demo效果。
使用Create React APP创建项目
你可以直接从Github上克隆我创建的示例:
git clone git@github.com:airen/css-modules.git
cd css-modules
cd react-modules
安装工程所需要的包:
npm i
npm start
这样工程就可以跑起来了。或者你使用:
npx create-react app <project-name> // 我这里创建的项目名称react-modules
cd project-name
npm start
你将看到的初始效果如下:
上面的这一切都并不重要,重要的是下面的内容 —— React中CSS Modules的使用。
注意,我写使用案例时的基本环境是
node: v10.9.0
、npm: v6.9.0
、Create React APP(v2)
。不同环境,估计效果略有差异,最终以你本地电脑运行结果为准。
使用Create React App 第二版本构建的React项目,在使用CSS Modules时,不需要做任何的配置。只需要创建.css
、.sass
或.sass
文件时有相应的要求,即**使用 [name].module.css
文件命名约定支持 CSS Modules 和常规 CSS 。 CSS Modules 允许通过自动创建 [filename]\_[classname]\_\_[hash]
格式的唯一 classname
来确定 CSS 的作用域。同样的,如是要是.sass
或.scss
的话,文件名格式应该是[name].module.sass
或 [name].module.scss
。
或许你会好奇,不需要配置就具备CSS Modules运行环境吗?事实上的确如此。当然,Create React APP环境默认配置了一些功能,如果这些功能达不到你工程所需的要求,那就需要手动进行配置。只不过Create React APP的配置文件隐藏的较深,需要执行不可逆转的命令:
npm run eject
执行完上面的命令之后,会新增两个目录script/
和config/
。
在config/
目录下可以看到两个配置文件webpack.config.js
和webpackDevServer.config.js
。如果你需要配置所需的功能,可以在webpack.config.js
中添加配置。如果你需要将项目打包输出的话,还得配置webpack.config.prod.js
。具体如何配置,这里不说了。因为太复杂了。在后面的内容,我们会聊聊Webpack中怎么配置CSS Modules(纯Webpack环境之下,即不依赖Create React APP构建的项目工程)。
CSS Modules的基本使用
前面提到过了,Create React APP默认具备了CSS Modules的功能:
在接下来的内容把重点放在CSS Modules的使用上。不管是在哪种环境之下使用,CSS Modules的使用都不会有太大差异,只会稍微的细节上的差异。
CSS Modules中类名的使用
将分支切换到
demo1
查看示例代码。
类名的使用是CSS中最基础的部分,那么CSS Modules中类名又是如何使用呢?第一个示例就向大家演示CSS Modules中类名的使用。
首先创建了一个基本组件Button
:
|--src/
|----Button/
|------Button.js // 组件模板在这个文件中构建
|------Button.module.css // CSS代码在这个文件中书写
前面提到过了,Create React APP中使用默认的CSS Modules功能,创建.css
文件时需要以[name].module.css
格式创建,正如上面示例中的Button.module.css
所示。
这个组件非常简单,就是一个按钮:
// Button.js
import React from 'react';
import styles from './Button.module.css';
class Button extends React.Component {
render() {
return (
<div className={styles.button} role="button">Click Me</div>
)
}
}
export default Button;
Button.module.css
中的代码也非常的简单:
// Button.module.css
.button {
--primary: #fe90af;
--color: #fff;
background: var(--primary);
color: var(--color);
padding: 5px 10px;
border-radius: 4px;
margin: 5px;
}
调用Button
组件之后,编译出来的HTML会像下面这样:
<div class="Button_button__1o_YA" role="button">Click Me</div>
对应的CSS的选择器.button
编译成了Button_button__1o_YA
,而CSS样式编译成:
.Button_button__1o_YA {
--primary: #fe90af;
--color: #fff;
background: var(--primary);
color: var(--color);
padding: 5px 10px;
border-radius: 4px;
margin: 5px;
}
效果如下:
CSS Modules将本地名.button
编译成全局名.Button_button__1o_YA
。在组件中(Button
)可以使用像.button
的名称来声明类名,而不必担心类名的冲突。如果你不信,可以写一个简单的小示例,假设在App.js
中使用一个带.button
类名的元素,看看是否会受组件Button
中的.buttonn
的影响:
<div className="button">我是一个带button类名的元素</div>
渲染出来的结果告诉我们,组件中的类名不会影响别的组件中的同类名。这已经达到我们想要的两个目的 —— 解决CSS的命名冲突 和 不会污染全局。
顺便说一下,.Button_button__1o_YA
中的__1o_YA
是一个随机的hash
值,以确保具有相同名称的多个CSS Modules的唯一性。
在Webpack的配置文件
webpack.config.dev.js
中可以,配置CSS Modules编译器如何重写类名,并且hash
值是可选的。
在继续往下介绍CSS Modules中类的使用前先打断一下,我们在引用Button.module.css
是以JavaScript对象的方
如需转载,烦请注明出处:https://www.w3cplus.com/react/css-modules-in-react.html
如果文章中有不对之处,烦请各位大神拍正。如果你觉得这篇文章对你有所帮助,打个赏,让我有更大的动力去创作。(^_^)。看完了?还不过瘾?点击向作者提问!