什么是css模块化
模块化的意思是将一个复杂问题自顶向下逐层分成若干模块的过程。换句话说就是把一个大功能拆解成小功能。
css发展
css随着时代的发展也发生了很大的转变,主要经历了以下几个历程
- 手写原生CSS 手写原生css指的是在html文件中写行内样式,内嵌样式,link引入外部样式,@import导入样式的时代,那时候主要还是使用jquery,操作DOM的时代。
- 使用预处理器SASS/LESS 后来人们发现手写原生CSS的时候很多样式中的属性值不能复用,为了定位某个元素需要写长长的一段选择器,于是有了SASS/LESS,它支持变量,也支持嵌套,给开发人员提供了一种用js的思维去写css样式的方式。
- 使用后处理器postCSS
PostCSS是一个用JS插件转换CSS的工具。这些插件可以支持变量和mixin,转换未来的CSS语法,内联图片等等。
使用预处理器SASS的时候必须安装node-sass,而node-sass是基于webpack来安装的,这种安装方式特别慢,而且sass会污染全局变量,也不支持未来的css。因此出现了PostCSS。它具有以下几个方面的功能
- 使用下一代css语法
- 自动补全浏览器前缀
- 自动把px转成rem
- css代码压缩
这些功能都是基于安装PostCSS相关的插件就可以实现,PostCSS只是一个工具,本身不会对css进行操作,因此对css是没有影响的。
-
使用css modules css modules是我们现在开发大部分都在用的方式,将css拆解成很多小的模块,这样就可以复用,方便代码的维护和处理。下面会重点介绍css模块化
-
使用css in js css in js的思想是用js对象来描述样式,而不是css样式表。因为我们现在在写前端页面的时候基本上都是使用的框架,如vue.js或者react.js。目前国内使用的都是这两个框架。里面的页面都是一个个的组件构成的。使用css in js有利于组件的隔离,将展示组件和逻辑组件分离开。
不使用css模块化存在的问题
现在项目都是团队开发的,当代码耦合在一起,代码量特别大的时候,很不好维护。因此就提出了css模块化的思想。 以下是几个不使用css模块化存在的问题:
- css全局作用域污染
- js和css之间无法共享变量
- vue.js里面虽然可以给样式里面定义一个scoped,变成组件化,但是对于css模块化没有一个统一的方案
css模块化的作用
- 提高代码重用率
- 降低耦合
- 提高开发效率、减少沟通成本
- 易于维护
css模块化方案
要实现一套css模块化方案,主要可以围绕三个方面去进行:命名, css module, css in js.
命名
良好的命名可以解决css全局作用域被污染的问题。我们常用的一种命名方式是BEM,当然除了BEM,还有其他的css命名方法论,比如OOCSS, SMACSS, ITCSS, SUITCSS, Atomic CSS等。
BEM
它的命名规矩就是block-name__element-name--modifier-name,也就是.模块名__元素名--修饰器名。
<div class='test'>
<div class="test__header">我是头部元素</div>
<div class="test__body">
<div class="test__body--item">我是内容</div>
</div>
<div class="test__footer">我是底部盒子</div>
</div>
使用sass/less/stylus的父元素选择器&可以更高效的编写BEM
.test {
&__header {}
&__body {
$--item {}
}
&__footer {}
}
OOCSS
OOCSS(Object-Oriented CSS)即面向对象的 CSS,它借鉴了 OOP(面向对象编程)的抽象思维,主张将元素的样式抽象成多个独立的小型样式类,来提高样式的灵活性和可重用性。 它遵循两个基本原则
- 独立的结构和样式:不要将定位,尺寸等布局样式和字体、颜色等表现样式写在一个选择器中
- 独立的容器和内容:避免对位置的依赖,子元素即使离开了容器也能正确显示
<div class="size1of4 bgBlue solidGray mt-5 ml-10 mr-10 mb-10"></div>
.size1of4 { width: 25%; }
.bgBlue { background: blue; }
.solidGray { border: 1px solid #ccc; }
.mt-5 { margin-top: 5px; }
.mr-10 { margin-right: 10px }
.mb-10 { margin-bottom: 10px; }
.ml-10 { margin-left: 10px; }
这种方式要合理应用,不然会导致出现太多类名。
SMACSS
SMACSS按照部件的功能特性,将其划分为五类
- 基础:定义html元素的默认样式。不需要前缀
- 布局:布局将页面分为几部分,可作为高级容器包含一个或多个模块。前缀是l-或者layout-
- 模块:对象或块,是可重用的模块化部分。前缀是m-或模块的名字
- 状态:任意模块或布局在特定状态下的外观。前缀是is-
- 主题:也就是换肤,描述了页面的外观。前缀是theme-
<form class="layout-grid">
<div class="field">
<input type="search" id="searchbox" />
<span class="msg is-error">There is an error!</span>
</div>
</form>
ITCSS
它兼容BEM, OOCSS, SMACSS关于选择器的命名方法论。使用分层的思想来管理我们的css文件。这种思想其实在我们实际项目开发中经常会用到。 ITCSS将css的样式规则划分为以下几个层次
- settings: 这个层次包含项目中用到的变量,比如颜色,字体大小
- tools: 这个层次包含项目中使用的mixins和functions
- generic: 这个层次是最基本的设定,比如reset.css,normalize.css
- base: 这个层次包含最基础的元素。比如img, link, p
- Objects: 这个层次包括设计模式。 比如水平居中,垂直居中,多列布局
- components: 这个层次包括UI组件,比如button, switch, slider
- trumps: 这个层次用于辅助和微调的样式
css modules
前面介绍的这些基于命名和分文件都是我们以前经常会使用的,但是随着自动化的环境下,这种方式已经不是那么适用了,因此出现了css modules的概念。 css modules允许我们像import js Module一样可以import css Module。每一个css Module都是一个独立的模块,每一个类名都是该模块导出对象的一个属性,并且css modules在打包时会自动将id和class加上全局唯一的hash值,从而避免命名冲突的问题。
css modules具有以下几个主要特性
- 作用域:模块中的名称默认都属于本地作用域,定义在 :local 中的名称也属于本地作用域,定义在 :global 中的名称属于全局作用域,全局名称不会被编译成哈希字符串。
- 命名:对于本地类名称,CSS Modules 建议使用 camelCase 方式来命名,这样会使 JS 文件更干净,即 styles.className。
- 组合:使用 composes 属性来继承另一个选择器的样式,这与 Sass 的 @extend 规则类似。
- 变量:使用 @value 来定义变量,不过需要安装 PostCSS 和 postcss-modules-values 插件。
/* style.css */
:global(.card) {
padding: 20px;
}
.article {
background-color: #fff;
}
.title {
font-size: 18px;
}
// App.js
import React from 'react'
import styles from './style.css'
export default function App() {
return (
<article className={styles.article}>
<h2 className={styles.title}>Hello World</h2>
<div className="card">Lorem ipsum dolor sit amet.</div>
</article>
)
}
编译结果
<style>
.card {
padding: 20px;
}
.style__article--ht21N {
background-color: #fff;
}
.style__title--3JCJR {
font-size: 18px;
}
</style>
<article class="style__article--ht21N">
<h2 class="style__title--3JCJR">Hello World</h2>
<div class="card">Lorem ipsum dolor sit amet.</div>
</article>
可以看出,其实编译过程也是使用了BEM的命名方法。
在vue项目中使用css modules
在vue项目中,实现css模块化有两种方式。vue loader支持scoped css和css modules。 scoped css是vue loader默认支持的,不需要额外配置和安装。直接在.vue文件中的就可以使用了。这个我们应该很熟悉了。添加scoped意味着当前style里面的样式只能在当前组件作用域中使用,其他的组件是没法使用这个样式。 css modules被vue loader视为scoped css的替代方法,需要加一些配置才能生效。
// vue.config.js
css: {
// 开启CSS Modules
requireModuleExtension: true;
loaderOptions: {
// 向loader传递配置选项
css: {
modules: {
// 自定义类命名规则
// 在我们的项目中,对类的命名是根据环境不同而改变的,开发环境中会展示该类的文件路径方便调试,其他环境中会 使用hash值封装
localIdentName: process.env.NODE_ENV === 'development' ? '[path][name]__[local]' : '[hash:base62:8]',
}
}
}
}
在vue项目中使用
<h1 :class="$style.colorBlack">我是标题1</h1>
<h2 :class="$style.colorBlue">我是标题2</h2>
<h3 :class="$style.colorRed">我是标题3</h3>
<style module>
.colorRed {
color: red;
}
.colorBlue {
color: blue;
}
.colorBlack {
color: rgb(19, 18, 18);
}
</style>
css modules还有一些其他的特性,比如:composes, :global选择器, :local选择器等等,感兴趣的可以查看官网
scoped css 和 css modules的比较
既然谈到了css modules是scoped css的替代方案,那我们就来介绍一下它们之间的区别 Vue Loader默认使用CSS后处理器PostCSS来实现Scoped CSS,原理就是给声明了scoped的样式中选择器命中的元素添加一个自定义属性,再通过属性选择器实现作用域隔离样式的效果。注意通过v-html创建的DOM内容是不受Scoped CSS控制的,如果希望修改其中的样式,可以通过深度作用选择器。 使用深度选择器的方式
- /deep/: 已废弃
- >>>: 不使用sass预处理器的时候生效
- ::v-deep: 使用sass预处理器的时候生效
scoped css 和 css modules的区别
| CSS Modules | Scoped CSS |
|---|---|
| 需要在vue.config.js中额外配置 | Vue Loader默认支持,无需额外配置 |
| 通过根据配置的类命名规则,为元素生成独一无二的类名来实现作用域隔离 | 通过给元素自定义hash属性,再使用属性选择器选中元素来实现作用域隔离 |
| 在style标签中声明module | 在style标签中声明scoped |
| 支持导入其他module的样式,支持样式组合 | / |
| 通过:global()来解除作用域的隔离,使样式在全局生效 | 1. 可以定义全局样式,使样式不受作用域约束;2. 可以通过深度作用选择器命中子组件,从而控制子组件的样式 |
css in js
css in js是把css写在js里的一种模式,类似于jsx(把html写在js里,html in js)。这些方式看起来好像和以前所说的结构,样式, JS要分离(关注点分离)的理念好像不是很符合。其实不是的,这个要围绕当前技术环境来说。因为现在基于vue.js和react.js框架开发项目的时候都是组件化开发,这样可以实现组件复用。慢慢地这种方式就变成了一种主流。 这种css in js的方式在react中使用的比较多,在vue中用的比较少,因为在vue中每一个.vue文件中就可以写一个单独的style了,已经实现了相对成熟的组件化了。 现在css in js的库在市面上有很多,比较推荐使用的是styled-components库。这个组件库目前只能使用在react项目中,如果想要在vue项目中使用类似的库,可以使用vue-styled-components,这是专门为vue打造的一款css in js 库。
styled-components的使用
- 安装
npm install --save styled-components
- 基本使用
// styles.js
import styled, { css } from 'styled-components'
// 创建一个名为 Wrapper 的样式组件 (一个 section 标签, 并带有一些样式)
export const Wrapper = styled.section`
padding: 10px;
background: deepskyblue;
`
// 创建一个名为 Title 的样式组件 (一个 h1 标签, 并带有一些样式)
export const Title = styled.h1`
font-size: 20px;
text-align: center;
`
// 创建一个名为 Button 的样式组件 (一个 button 标签, 并带有一些样式, 还接收一个 primary 参数)
export const Button = styled.button`
padding: 10px 20px;
color: #333;
background: transparent;
border-radius: 4px;
${(props) => props.primary && css`
color: #fff;
background: blue;
`}
`
// App.js
import React from 'react'
import { Wrapper, Title, Button } from './styles'
// 然后,像使用其他 React 组件一样使用这些样式组件
export default function App() {
return (
<Wrapper>
<Title>Hello World, this is my first styled component!</Title>
<Button>Normal Button</Button>
<Button primary>Primary Button</Button>
</Wrapper>
)
}
- 高级使用
- 可以通过插值的方式给样式组件传递参数(props),这在需要动态生成样式规则时特别有用。
- 可以通过构造函数 styled() 来继承另一个组件的样式。
- 使用 createGlobalStyle 来创建全局 CSS 规则。
- styled-components 会为自动添加浏览器兼容性前缀。
- styled-components 基于 stylis(一个轻量级的 CSS 预处理器),你可以在样式组件中直接使用嵌套语法,就像在 Sass/Less/Stylus 中的那样。
- 强烈推荐使用 styled-components 的 Babel 插件 babel-plugin-styled-components(当然这不是必须的)。它提供了更好的调试体验的支持,比如更清晰的类名、SSR 支持、压缩代码等等。
- 你也可以在 Vue 中使用 styled-components,vue-styled-components,不过好像没人会这么做~
- 默认情况下,模版字符串中的 CSS 代码在 VSCode 中是没有智能提示和语法高亮效果的,需要安装 扩展。
参考官方文档
vue-styled-components的使用
- 安装
npm i -S vue-styled-components
- 基本使用
// Flex.js
import styled from 'vue-styled-components'
const Flex = styled.div`
display: flex;
align-items: center;
justify-content: center;
`
export {
Flex
}
// App.vue
<template>
<flex>
<p>styled</p>
<span>components</span>
</flex>
</template>
<script>
import { Flex } from '@/components/Flex'
export default {
name: 'child1',
components: {
Flex
}
}
</script>
- 高级使用