css在react中的进化史
首先看一下传统html标准下的css解决方案的不足。
- 全局样式容易造成命名冲突,特别是使用第三方库时。
- 在web开发组件化的大势下,css语言对组件化。
- 样式与状态相关的情况越来越多,需要能访问组件状态的动态样式。
BEM
为了避免命名冲突,早在2006年,yandex就发明了BEM。
BEM是一种css命名规范,它的核心思想是将css的命名空间划分为三个部分:块(block)、元素(element)和修饰符(modifier)。
1 | .opinions-box { |
使用BEM可以很大程度上避免命名冲突,并且能够很好的表达组件的结构。但它仍然是全局的,所以它只能成为一个规范而不是解决方案。
react原生方案
react提供的方案是className和inline style属性。
1 | <img |
虽然这里的style的类型定义是csstype的CSS.Properties<string | number>
,但其实react并不支持css中的媒体查询和伪类。
第一代css in js
虽然可以通过嵌套css选择器或者使用BEM来避免命名冲突,但这都不彻底。为了能彻底解决这个问题,大约在2014年出现了第一代css in js:jss。
jss的实现原理很简单:当样式转换成css时,它会产生一个唯一的类名挂载在style标签下(可使用document.styleSheets命令查看),用户可以完全不用担心命令冲突。
而且因为它是使用js在运行时生成css,所以可以依据组件状态来动态生成。同时,jss还支持媒体查询和伪类。
1 | import { createUseStyles } from 'react-jss' |
css in js一下子就解决了上面提到的全部三个问题,但它也不是银弹:
- 因为是运行时生成的,自然也就无法使用css预处理器和postcss等工具链;
- 如果css依赖的状态变化得很频繁,可能会导致性能问题;
- 可读性和可复用性都不如css;
css module
jss是在运行时生成css,在css in js大步发展的同时,在构建时生成css的方案也出现了,这就是css module。
在2015年由webpack的css-loader提出并实现。
css module是一个默认作用域为局部的css模块文件。它在构建时,会将css中的类名转换成唯一的类名,然后在js中导出一个对象,对象的key是原来的类名,value是转换后的类名。
1 | /* style.css */ |
1 | import styles from './style.css'; |
最终打包出来的css类名就是一长串hash值:
1 | ._2DHwuiHWMnKTOYG45T0x34 { |
代码中引用的styles.className
就是_2DHwuiHWMnKTOYG45T0x34
。
因为是构建时生成的,所以css module可以使用css预处理器和postcss等工具链。比如babel-plugin-react-css-modules可以让className
不必一定为驼峰命名,可以为传统的class-name
形式。
css module完美的解决了命名冲突和组件化的问题,但因为它是构建时生成的静态css,所以无法使用状态来动态生成样式。
但很多时候我们其实并不需要连续的样式,绝大多数情况下离散的样式已经能满足需求。
比如我们需要根据错误的数量来改变错误提示的颜色,我们用jss可以这样写:
1 | const useStyles = createUseStyles({ |
但其实我们只分了6级,完全可以定义6个类名,然后根据错误数量来动态添加类名:
1 | .red0 { |
1 | return <p className={classes['red' + Math.min(errorCount, 5)]}>there are {errorCount} errors.</p> |
类似red0
到red5
这样的类,可以称之为原子化的css。原子化的css可以很好的解决动态样式的问题,而且它的可读性和可复用性都很好。
原子化css
原子化css的思想是将css样式拆分成最小的单元,然后通过组合来实现样式的复用。
最早由雅虎在2015年提出Atomic CSS这个概念。后来由tailwindcss发扬光大。
原子化css的兴起,正是因为组件化。有了组件来负责原子化css的封装,我们就可以隔离原子化css的复杂性。
1 | const Button = ({ children, color }) => ( |
使用如下:
1 | <Button color='hot-pink'> 注册 </Button> |
第二代css in js
在jss之后,css in js的发展又迎来了第二代,这一代的代表是emotion。在它之前还有styled-components和styled-jsx。
模板字符串
es6引入了模板字符串,这使得css in js的实现变得更加简单,写法更接近css。
1 | import { css } from '@emotion/css' |
更简便的API
emotion提供了更简便的styled API。
1 | import styled from '@emotion/styled' |
支持ssr
react 16加入了ssr支持,css in js也需要与时俱进。styled components、styled-jsx和emotion都支持ssr。