CSS发展、CSS模块化

61 阅读6分钟

了解CSS模块化之前,先了解一下CSS发展史,可以更好的了解CSS模块化

CSS 发展

我们在书写 CSS 的时候其实经历了以下几个阶段:

  • 手写源生 CSS
  • 使用预处理器Sass/Less
  • 使用后处理器 PostCSS 【目前不太了解,暂时不作说明】
  • 使用 css modules(CSS模块化)
  • 使用 css in js

手写源生 CSS

最初的css书写方式

  • 行内样式,即直接在 html 中的 style 属性里编写 css 代码。
  • 内嵌样式,即在 html 中的 style 标签内编写 css,提供给当前页面使用。
  • 导入样式,即在内联样式中 通过 @import 方法,导入其他样式,提供给当前页面使用。
  • 外部样式,即使用 html 中的 link 标签,加载样式,提供给当前页面使用。

为什么不建议使用行内样式?

使用行内样式的缺点:

  • 样式不能复用。
  • 样式权重太高,样式不好覆盖。
  • 表现层与结构层没有分离。
  • 能进行缓存,影响加载效率。

为什么不建议使用导入样式?

经测试,在 css 中使用 @import 会有以下两种情况:

  1. 在 IE6-8 下,@import 声明指向的样式表并不会与页面其他资源并发加载,而是等页面所有资源加载完成后才开始下载。
  2. 如果在 link 标签中去 @import 其他 css,页面会等到所有资源加载完成后,才开始解析 link 标签中 @import 的 css。

使用导入样式的缺点:

  • 导入样式,只能放在 style 标签的第一行,放其他行则会无效。
  • @import 声明的样式表不能充分利用浏览器并发请求资源的行为,其加载行为往往会延后触发或被其他资源加载挂起。
  • 由于 @import 样式表的延后加载,可能会导致页面样式闪烁。

使用预处理器 Sass/Less

随着时间的不断发展,我们逐渐发现,编写源生的 css 其实并不友好,例如:源生的 css 不支持变量,不支持嵌套,不支持父选择器等等,这些种种问题,催生出了像 sass/less 这样的预处理器。

预处理器主要是强化了 css 的语法,弥补了上文说了这些问题,但本质上,打包出来的结果和原生的 css 都是一样的,只是对开发者友好,写起来更顺滑。

CSS 模块化概念

随着 react、vue 等基于模块化的框架的普及使用,我们编写源生 css 的机会也越来越少。我们常常将页面拆分成许多个小组件,然后像搭积木一样将多个小组件组成最终呈现的页面。

但是我们知道,css 是根据类名去匹配元素的,如果有两个组件使用了一个相同的类名,后者就会把前者的样式给覆盖掉,看来解决样式命名的冲突是个大问题。

后面有详细讲解

CSS In JS

CSS in JS,意思就是使用 js 语言写 css,完全不需要些单独的 css 文件,所有的 css 代码全部放在组件内部,以实现 css 的模块化。

CSS in JS 其实是一种编写思想,目前已经有超过 40 多种方案的实现,最出名的是 styled-components。

使用方式如下:

import React from "react";
import styled from "styled-components";

// 创建一个带样式的 h1 标签
const Title = styled.h1`
  font-size: 1.5em;
  text-align: center;
  color: palevioletred;
`;

// 创建一个带样式的 section 标签
const Wrapper = styled.section`
  padding: 4em;
  background: papayawhip;
`;

// 通过属性动态定义样式
const Button = styled.button`
  background: ${props => (props.primary ? "palevioletred" : "white")};
  color: ${props => (props.primary ? "white" : "palevioletred")};

  font-size: 1em;
  margin: 1em;
  padding: 0.25em 1em;
  border: 2px solid palevioletred;
  border-radius: 3px;
`;

// 样式复用
const TomatoButton = styled(Button)`
  color: tomato;
  border-color: tomato;
`;

<Wrapper>
  <Title>Hello World, this is my first styled component!</Title>
  <Button primary>Primary</Button>
</Wrapper>;

可以看到,我们直接在 js 中编写 css,案例中在定义源生 html 时就创建好了样式,在使用的时候就可以渲染出带样式的组件了。

image.png

CSS模块化

模块化CSS是一种将CSS样式表的规则和样式定义封装到模块或组件级别的方法,以便于更好地管理、维护和组织样式代码。

为什么需要CSS Modules

  • 解决类名冲突问题
  • CSS的规则都是全局的,任何一个组件的样式规则,都对整个页面有效。因此,为了使用独特的样式,要保证元素的类名不冲突。这对于大型项目而言是很难的。
  • CSS Modules的思路:产生一个独一无二的class的名字,不会与其他选择器重名

拿Vue在style里添加scoped举例:

  • 作为开发人员想必都知道,在开发过程中如果同一个页面中我们使用的Class名重复了的话,就会比较对应重复项Class的权重。这样就可能会存在着对应的冲突。

  • 所以在Vue的页面中我们通常会给对应的style中添加上scpoed,也是同样的道理。

  • 使用scoped的原理就是在在我们预编译的时候,在添加上scoped的style中的每个CSS名前面添加一个哈希字段,即每个选择器都转换为.scoped-XXXXXX形式的选择器,其中XXXXXX是哈希类名。

  • 然后当模板在渲染的时候就会将对应的选择器插入到对应的根标签中。同时为了避免样式错乱,也会在根标签上携带同样的哈希字段。这样就可以避免CSS样式错乱。

实现原理

开启了css module后,构建工具webpack的css-loader会将样式中的类名进行转换,转换为一个唯一的hash值 image.png 由于hash值是根据模块路径和类名生成的,因此,不同的css模块,哪怕具有相同的类名,转换后的hash值也不一样。

根据导出结果可知,我们就可以在js代码中获取到css模块导出的结果,从而应用类名

// src/index.js
import style from  "./assets/style.css"
console.log(style)
const div = document.getElementById("div1");
div.className = style.c1;

打印结果如下: image.png

CSS 模块化的实现方式

CSS Modules 指的是我们像 import js 一样去引入我们的 css 代码,代码中的每一个类名都是引入对象的一个属性,通过这种方式,即可在使用时明确指定所引用的 css 样式。

并且 CSS Modules 在打包的时候会自动将类名转换成 hash 值,完全杜绝 css 类名冲突的问题。

使用方式如下:

1、定义 css 文件。

.className {
  color: green;
}
/* 编写全局样式 */
:global(.className) {
  color: red;
}

/* 样式复用 */
.otherClassName {
  composes: className;
  color: yellow;
}

.otherClassName {
  composes: className from "./style.css";
}

2、在 js 模块中导入 css 文件。

import styles from "./style.css";

element.innerHTML = '<div class="' + styles.className + '">';

3、配置 css-loader 打包。

CSS Modules 不能直接使用,而是需要进行打包,一般通过配置 css-loader 中的 modules 属性即可完成 css modules 的配置。

// webpack.config.js
module.exports = {
  module: {
    rules: [
      {
        test: /\.css$/,
        use:{
          loader: 'css-loader',
          options: {
            modules: {
              // 自定义 hash 名称
              localIdentName: '[path][name]__[local]--[hash:base64:5]',
            }
          }
       }
    ]
  }
};

4、最终打包出来的 css 类名就是由一长串 hash 值生成。

._2DHwuiHWMnKTOYG45T0x34 {
  color: red;
}

._10B-buq6_BEOTOl9urIjf8 {
  background-color: blue;
}