简介
根据官方的CSS模块GitHub仓库,CSS模块是一个CSS文件,其中所有的类名和动画名默认都在本地范围内。相比之下,在一个典型的CSS文件中,所有的CSS选择器都在全局范围内。
在本教程中,我们将研究一些在编写CSS时经常出现的问题,并学习CSS模块如何帮助我们避免这些问题。然后,我们将把CSS模块集成到一个React应用程序中。
让我们开始吧
前提条件
- 具有HTML和CSS的知识
- 对React的工作知识
熟悉CSS模块是一个额外的好处。
了解常见的CSS问题
CSS中的所有选择器都是全局变量。随着应用程序的扩展,使用全局变量和管理依赖关系变得越来越困难。当几个开发人员都在做这个应用时,事情就变得更加棘手了。
这就是原因。
名称碰撞
比方说,在设计博客风格时,我们添加了类名post 来表示主页上的文章。另一个开发者创建了侧边栏,也为侧边栏上的帖子添加了类名post 。像这样的错误会导致名称冲突,见这里。
/* styles */
.main .post {
color: #000000;
font-size: 2rem;
}
.sidebar .post {
color: #FFFFFF;
font-size: 1rem;
}
随着 应用程序的扩展, 你更有可能遇到名称碰撞 (可能 会损害性能)。
清理死代码的困难
当一个元素或React组件从我们的代码中被删除时,我们也需要删除其样式。然而,在大型应用程序中,要确定一个类是否在使用,可能非常困难。CSS并没有提供一个开箱即用的解决方案。
依赖性管理
在使用全局变量时,依赖关系没有明确的定义,因此很难确定哪些样式会被继承或通过组合应用。
在CSS中还有一些隐含的依赖关系,仅仅通过扫描代码是不容易识别的。例如,一个带有position: absolute 的元素是相对于它的父元素position: relative 的。
依赖关系是一个巨大的原因,维护我们的CSS代码的难易程度在很大程度上取决于我们的依赖关系的结构如何。
评估解决方案
BEM - Block Element Modifier是HTML和CSS中流行的类的命名规则,旨在帮助开发者理解两种语言之间的关系。BEM通过提供严格的命名规则解决了上述问题。
在BEM中,一个Block ,是一个独立的元素,它可以独立成章;它通常是一个父元素,如.btn{} 。一个Element 是指一个Block 的子元素;它没有独立的意义,在Block 的名称后面用两个下划线表示(例如,.btn__text )。
修饰符是Block 或Element 上的一个标志,用于对其进行样式化。它在Block 或Element 的名称后用两个连字符表示(例如,.btn--primary {} )。
// Block Element
.btn {}
// Element that depends on the Block often a child element
.btn__text {
// rules
}
// Modifiers that changes the styles of the block
.btn--primary {}
.btn--small {}
BEM命名方法的好处是,尽管是全局性的,但所有的选择器都被修改器所覆盖。然而,手动添加BEM命名是重复的、相当乏味的,而且容易出现人为错误。
你最终可能会花费大量的时间来弄清楚某些东西是Block 还是Element 。在我看来,Bulma CSS的创建者Jeremy Thomas完美地概括了这个问题。
开发涉及到将困难的问题自动化,所以我们应该能够通过合适的工具轻松实现自动化命名。
注意:尽管CSS模块使我们能够对我们的样式进行范围控制,但我们仍然可以通过在类名前加上""来声明全局类。
:global:
:global .title {
font-size: 2rem;
}
CSS模块的优势
大多数现代JavaScript和CSS的工作流程都趋向于基于组件的架构,但CSS的进展纯粹是传统的,实际上并不被语言所支持。
前面讨论的BEM就是一个完美的例子。一句被称为软件工程基本定理的熟语宣称:"计算机科学中的每一个问题都可以通过一个额外的抽象层来解决"。
CSS模块是一个薄薄的抽象层,它封装了引入语言的新概念。因此,CSS模块的写法就像普通的CSS一样,如下面的代码片段所示。
/* styles.css */
.title {
font-size: 2rem;
font-weight: bold;
color: red;
}
.text {
font-size: 1.2rem;
font-weight: 500;
color: blue;
}
一个区别是,在CSS模块中,我们所有的标记都写在一个JavaScript文件中,如index.js 。
import styles from "./styles.css";
document.getElementById("app").innerHTML = `
<h1 class=${styles.title}>Hello Vanilla!</h1>
<div class=${styles.text}>
We use the same configuration as Parcel to bundle this sandbox, you can find more
info about Parcel
</div>
`;
当我们从index.js 文件中导入我们的CSS模块时,CSS模块会导出一个带有从本地名称到全局名称的映射的对象。
{
title: "_src_styles__title",
text: "_src_styles__text"
}
我们可以看到,CSS模块动态地生成了独特的类名,使我们整个团队的命名自动化。
CSS模块如何工作
像webpack、Browsify和JSPM这样的现代工具使我们能够明确地定义跨语言的依赖关系。因此,我们可以明确地描述每个文件的依赖关系,无论源文件的类型如何。
在下面的代码片段中,只要加载或捆绑MyComponent ,相应的CSS就会像其他依赖关系一样被加载。
import './my-component-name.css';
const MyComponent = () => {
// component codes
}
export default MyComponent;
CSS模块包括这种新技术,这是现代加载器的关键能力。然而,在基本层面上,需要一个新的规范来描述这些符号的共享方式。
了解ICSS
尽管CSS模块是像普通的CSS一样编写的,但它实际上编译成了一种叫做ICSS(Interoperable CSS)的低级可互换格式,它是为加载器实现者而不是最终用户设计的。它是标准CSS的超集,是增强CSS的低级文件格式。
你可以将CSS模块纳入广泛的Automa应用程序中,然而,我们将为一个React应用程序设计样式。
用CSS模块设计React应用程序的样式
Create React App v2(及更高版本)开箱即支持CSS Module。我们所要做的就是使用下面的命名规则。
[name].module.css
让我们通过建立一个简单的React应用来看看它的实际效果吧!首先,让我们启动并运行我们的应用程序。
npx create-react-app button-stack
cd botton-stack
npm start
接下来,我们将为我们的应用程序添加CSS模块支持,只需将App.css 文件重命名为App.module.css 。更新App.js 文件中的导入语句以避免错误。
.shadow {
box-shadow: rgba(50, 50, 50, 0.2) 0 5px 5px 0;
}
.app {
display: flex;
justify-content: space-around;
}
.title {
margin-top: 25%;
text-align: center;
}
将Index.css 文件更新为Index.module.css ,以及Index.js 文件中的导入语句。接下来,在我们的App.js 文件中,添加以下代码。
import { title, app } from './App.module.css';
import Button from './components/Button';
function App() {
return (
<div>
<h1 className={title}>CSS Module Buttons</h1>
<article className={app}>
<Button />
</article>
</div>
);
}
export default App;
尽管这段代码的大部分内容应该是熟悉的,但有几件事情我们需要注意一下。首先,我们正在对title 和app 进行解构。我们需要的样式来自styles 对象,它由CSS模块导出。
现在,我们需要创建Button component 。在src 目录中,创建一个components 文件夹。在该文件夹中,创建一个Button.js 和一个Button.module.css 文件;在Button.module.css 文件中添加以下代码。
.normal-button {
display: inline-flex;
line-height: 2;
text-align: center;
padding: 1px 60px;
font-family: "IBM Plex Sans";
font-size: 1rem;
font-weight: 500;
border-radius: 4px;
cursor: pointer;
composes: shadow from "../App.module.css"
}
.danger {
composes: normal-button;
background-color: rgb(255, 8, 8);
border: 2px solid rgb(255, 8, 8);
color: white;
}
.secondary {
composes: normal-button;
background-color: rgb(128, 118, 118);
border: 2px solid rgb(128, 118, 118);
color: white;
}
.info {
composes: normal-button;
background-color: rgb(6, 218, 255);
border: 2px solid rgb(6, 218, 255);
color: white;
}
.warning {
composes: normal-button;
background-color: rgb(248, 202, 49);
border: 2px solid rgb(248, 202, 49);
color: #ffffff;
}
.success {
composes: normal-button;
background-color: rgba(30, 156, 41, 0.966);
border: 2px solid rgba(30, 156, 41, 0.966);
color: white;
}
.primary {
composes: normal-button;
background-color: rgba(33, 124, 243, 0.849);
border: 2px solid rgba(33, 124, 243, 0.849);
color: #FFFFFF;
}
在这个文件中,我们有一个普通的按钮类.normal-button ,它由App.module.css ,组成了shadow class 。
组合是CSS模块中的一个功能,它使我们能够对选择器进行组合。因此,我们可以通过从另一个类中继承样式来组成一个类,但这些composes 规则必须在其他规则之前。
例如,.danger,.info,.primary,.warning, 和.success 类都通过组合继承了来自.normal-botton 的样式。
我们的App.js 文件现在应该看起来像下面的代码。
import { title, app } from './App.module.css';
import Button from './components/Button';
function App() {
return (
<div>
<h1 className={title}>CSS Module Buttons</h1>
<article className={app}>
<Button />
</article>
</div>
);
}
export default App;
我们的应用程序显示应该看起来像这里的图片。
你可以查看本教程的完整代码。
总结
毫无疑问,CSS模块为CSS语言提供了多年来最重要的改进之一CSS模块最好的一点是,我们可以写出好的老式CSS,并将其纳入各种应用中。它只是为CSS增加了更多的力量
如果你的React应用没有使用Create React App,或者它使用的版本低于第2版,你仍然可以通过使用 [babel-plugin-react-css-module](https://github.com/gajus/babel-plugin-react-css-modules).
深入了解CSS模块 》一文首次出现在LogRocket博客上。