74

React中CSS Modules的使用

 5 years ago
source link: https://www.tuicool.com/articles/A3mu22I
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

最近项目开始换React的工程,感觉好多东西都得重新开始,特别在撸CSS的时候。说实话和Vue的工程相比,体感差完了。在Vue的工程中除了 Modules之外还可以使用CSS的作用域 scoped 的概念。用久了Vue的同学,在这方面的感觉爽死了,但是突然切到React的工程体系之下,这方面的感觉突然不要不要。拿个实例来说吧(可能我做得不到位),组件的CSS是全局的,有时覆盖起来,除了蛋疼之外,而费时,费成本。

React项目中写CSS的姿势

比如最简单的一个按钮组件,居然要这么撸:

yUV7R3a.png!web

虽然为每个组件创建一个单一的 .scss 文件,并在入口引入相应的样式文件,发现React中的CSS没有域的概念,是全域的。

6V3uYff.png!web

很多时候需要去覆盖组件初始样式,不得不重新定义样式类或提高选择器权重来处理。着实的蛋疼。除此之外,项目是多人开发,各种各样的类名都有,未统一起来,从覆盖上也增加了不少工作量。另外,还会碰到一些常见的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效果

Uri2MzI.png!web

使用Create React APP创建项目

你可以直接从Github上克隆我创建的示例:

git clone [email protected]: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

你将看到的初始效果如下:

veMJJj2.png!web

上面的这一切都并不重要,重要的是下面的内容 —— 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/

7NNvQnF.png!web

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的功能:

NnyYJb2.png!web

在接下来的内容把重点放在 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;
}

效果如下:

AnMfAby.png!web

CSS Modules将本地名 .button 编译成全局名 .Button_button__1o_YA 。在组件中( Button )可以使用像 .button 的名称来声明类名,而不必担心类名的冲突。如果你不信,可以写一个简单的小示例,假设在 App.js 中使用一个带 .button 类名的元素,看看是否会受组件 Button 中的 .buttonn 的影响:

<div className="button">我是一个带button类名的元素</div>

7vMfq2A.png!web

渲染出来的结果告诉我们,组件中的类名不会影响别的组件中的同类名。这已经达到我们想要的两个目的 —— 解决CSS的命名冲突不会污染全局

顺便说一下, .Button_button__1o_YA 中的 __1o_YA 是一个随机的 hash 值,以确保具有相同名称的多个CSS Modules的唯一性。

在Webpack的配置文件 webpack.config.dev.js 中可以,配置CSS Modules编译器如何重写类名,并且 hash 值是可选的。

在继续往下介绍CSS Modules中类的使用前先打断一下,我们在引用 Button.module.css 是以JavaScript对象的方


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK