聊聊我理解的CSS-in-JS(二)

798 阅读8分钟

我正在参加「掘金·启航计划」,这篇文章我主要想从 CSS-in-JS 讲开去,谈谈我理解的 CSS-in-JS 的理念以及当下前端工程化中的主流 CSS 方案。

在我的上一篇博客聊聊我理解的 CSS-in-JS(一) 中谈到传统 CSS 在当下现代化前端开发工程中面临的困境,在 Web 开发早期,HTML、JS、CSS 作为 Web 开发的三驾马车各自承担着不同的角色,天然形成了“关注点分离”的设计原则,但是随着 Web 前端开发的迅速发展,社区涌现了非常多的 CSS 框架/工具/方案来补足传统原生 CSS 在组件化工程化中的不足,本文想从这些不同框架或方案的入手聊聊我理解其中理念的变迁。

一、从关注点分离到关注点混合

1、早期的实现

在最早期的设计中 HTML、CSS、JavaScript 的关注点是非常清晰的,分别承担网页中语义层、视觉层、逻辑层的实现,而前端开发的本质是接收用户的操作作为输入,然后改动数据并将数据映射到 HTML 中,具体流程如下图所示: image.png 随着前端交互场景的越来越重,JS 与 HTML 之间的“交互”变得愈加频繁,而且随着对交互样式的要求越来越高,动态样式的需求也开始提上桌面。

2、新的变革

随着 Vue、React 等前端开发框架/库的大火组件化的开发理念也愈发深入人心,同时 Vue 的单文件、React 的 JSX 都在为我们提出了一个貌似“离经叛道”的新选择:关注点混合。将 HTML 与 JS 混合到了一起,JSX 中 “HTML” 甚至成为了 JS 变量的一部分,这种所谓的“关注点混合”的开发模式最开始饱受诟病,很多人认为违背了“关注点分离”的开发原则,但是现在看来这是一种更好的“融合于一”的组件化开发思路,将数据的视图层与逻辑层合在一起更有利于我们以合适的粒度拆分开发组件。

好像,我们漏了点什么?那 CSS 呢?到目前为止,它好像仍游离在 JS 的框架体系之外。

二、CSS 的发展历程

1、最初的 CSS

在最早期还没有 CSS 时,主流的浏览器厂商通过给 HTML 添加各式各样的标签来满足一定的排版需求,比如 <b> 让文字成为粗体,<i> 则是斜体;还可以通过改变标签的属性来更改样式,包括改变字号 <font size="+2">, 改变字体 <font color="blue">,改变元素颜色 <body bgcolor="red">。所有的样式是通过 HTML 标签来进行完成。但是这种写法极难维护,样式与页面结构深度绑定,因此 CSS 作为一门独立的 HTML 的样式语言就呼之欲出了,CSS 使得样式信息可以独立于页面结构(HTML),保存到一个单独的文件中。对样式的改动则可以影响网站中多个甚至所有页面的外观,

2、CSS 扩展语言的兴起

CSS 语言本身存在如下缺陷:

  • 语法不够强大,比如无法嵌套书写导致模块化开发中需要书写很多重复的选择器;
  • 没有变量和合理的样式复用机制,使得逻辑上相关的属性值必须以字面量的形式重复输出,导致难以维护;
  • 所有的样式都是全局的,没有作用域的概念,同时没有模块化的处理方案。

为了解决上述问题社区先后涌现了非常多 CSS 扩展语言,Sass(2007)、Less(2009) 和 Stylus(2010),都提供了 CSS 缺失的样式层复用机制,CSS 所缺少的逻辑处理,和变量系统。同时也对 CSS 的语法做了很多的优化,比如说支持嵌套,利用缩进、空格和换行来减少需要输入的字符。

Less、Scss(Sass) 和 Stylus 代码并不能被浏览器直接解析,所以必须先将它们编译成 CSS 代码,我们在实际项目开发中可以采用现有框架已经提供了的 CSS 预处理器选项,比如 webpack 提供的相应 loader、rollup 提供的相应插件等等,编译相关配置会自动帮我们生成。

但是 CSS 预处理器并没有能够从根本上解决 CSS 的一些问题,比如说它们给 CSS 增加了变量,但和 JS 的变量并不互通,我们需要分别在 JS 和 CSS 中维护两套变量,甚至他们维护的变量相同;再者就是它们并没能力解决 CSS 的模块化的问题,这个问题的解决还需要借助 JS 能力。

3、CSS Modules

CSS Modules 不是将 CSS 改造成编程语言,而是在 CSS 的基础上通过构建工具对其进行了扩展,它更像是提供了一个解决思路帮助解决样式作用域等一系列问题,在我的上一篇文章中已经有详细介绍,此处不再展开。

4、CSS-in-JS

React 的 JSX 将前端开发模式从三架马车分离并行,到了 HTML 骑上 JS 前进,于是又有人开始考虑,既然 HTML 可以,我 CSS 凭什么不能骑呢? 因此为了用 JS 赋予 CSS 状态与逻辑,将 CSS 纳入 JS 的管理范畴,CSS-in-JS 的方案应运而出。

这里我想与我的上一篇博客聊聊我理解的 CSS-in-JS(一)不同,用一种更高的角度来聊聊 CSS-in-JS。

从字面上看,CSS-in-JS 就是在 JS 里写 CSS,反过来说 CSS 需要 JS 才能起作用。原生的 JS 操作 CSS 无外乎下面五种方式:

  1. 通过 DOM API 设置元素的 style 属性,为元素加入内联(Inline)样式;
  2. 通过 DOM API 设置元素的 className 属性,为元素关联现有的类(Class)样式;
  3. 通过 DOM API 在页面文档中动态插入包含 CSS 规则文本的 <style> 标签;
  4. 第 3 条的变体:通过 CSSOM 的 CSS StyleSheet 对象动态修改页面中的 CSS 规则;
  5. 非运行时方案:在编译阶段把 JS 中的 CSS 通过 AST(Abstract Syntax Tree,抽象语法树)剥离出来,生成静态 CSS 文件并在页面中引用。

因此无论是哪一种 CSS-in-JS 框架,它们的内部实现最终都会落地于以上五种方式之一或组合。

事实上,不同 CSS-in-JS 框架之间不仅仅是内部实现方式的大同小异,其使用 API 也愈发形成事实上的统一,目前最受欢迎的两个解决方案是 emotion 和 styled-components 在接口上使用同样的接口设计:CSS prop 与样式组件:

// CSS prop
import { css } from '@emotion/css'

const color = 'white'

render(
  <div
    className={css`
      padding: 32px;
      background-color: hotpink;
      font-size: 24px;
      border-radius: 4px;
      &:hover {
        color: ${color};
      }
    `}
  >
    Hover to change color.
  </div>
)
import styled from '@emotion/styled'

const Button = styled.button`
  padding: 32px;
  background-color: hotpink;
  font-size: 24px;
  border-radius: 4px;
  color: black;
  font-weight: bold;
  &:hover {
    color: white;
  }
`

render(<Button>This my button component.</Button>)

这两种 API 接口模式代表着两种组件化样式风格。css prop 可以算是内联样式的升级版,用户定义的内联样式以 JSX 标签属性的方式与组件紧密结合,可以帮助用户快速迭代开发,让用户可以更快速的定位问题。不过由于样式直接内嵌在 JSX 中,势必在一定程度上会影响组件代码的可读性。

样式组件更像是 CSS 的组件化封装,将样式抽象为语义化的标签,把样式从组件实现中分离出来,让 JSX 结构更“干净整洁”。相对而言,样式组件定义的样式不如内联样式更方便直接,而且需要给额外多出来的样式组件定义新的标签名,会在一定程度上影响开发效率;但从另外一个角度来说,样式组件以更规范的接口提供给团队复用,适合有成熟确定的设计语言的组件库或是产品。

5、原子化CSS

原子化 CSS 是一种 CSS 的架构方式,它倾向于小巧且用途单一的 class,并且会以视觉效果进行命名。目前比较流行的有 Tailwind CSSWindi CSS 以及 Tachyons 等等。对于现代前端开发工程中 CSS 遇到的问题它给出了自己的解决办法:原子类。通过原子类的组合、扩展实现对组件样式的定义。

相较于需要配置 Webpack loader、Babel plugin 的 CSS-in-JS 方案,理想的原子类只需在全局引入一个 .css 文件就能立即使用。而且原子类由于本身仅仅是 class,在各框架中都支持,能无缝迁移。当然其也拥有一些缺点比如上手难度过高等。

三、总结

从某种角度看,前端技术体系终究需要一个角色来维护通用的状态,在某一个历史阶段为了解决特定的问题每一个解决方案的出现似乎也成了历史必然。CSS-in-JS 终究算是一门比较新颖的技术方案,它既有好处也有自身的缺点,在当下前端开发背景下也不失为一个较好的选择。总之还是要根据项目的实际情况,而非为了使用一个技术而用一个技术。

参考文章

CSS-in-JS:一个充满争议的技术方案

A Thorough Analysis of CSS-in-JS