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-componentsstyled-jsxreact-style
  • CSS功能模块(Functional CSS),比如tailwindcsstachyons等(怎么看都有点类似早期的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经过这么多年的发展,从SMACSSOOCSS,再到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。

接下来的内容主要来看如何在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.0npm: v6.9.0Create 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.jswebpackDevServer.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对象的方

剩余80%内容付费后可查看
* 请输入阅读码(忘记阅读码?

如需转载,烦请注明出处:https://www.w3cplus.com/react/css-modules-in-react.html

如果文章中有不对之处,烦请各位大神拍正。如果你觉得这篇文章对你有所帮助,打个赏,让我有更大的动力去创作。(^_^)。看完了?还不过瘾?点击向作者提问!

赏杯咖啡,鼓励他创作更多优质内容!
返回顶部