原子化CSS的利弊权衡:如何平衡效率与复杂性

734 阅读9分钟

什么是原子化 CSS

首先,我们为原子化 CSS 给出一个定义:

原子化 CSS 是一种 CSS 架构方式,倾向于使用小巧且用途单一的 class,并以视觉效果进行命名。

本质上,你可以将其理解为一种工具类 CSS。例如:

.m-0 {
  margin: 0;
}
.text-red {
  color: red;
}

使用示例:

<div class="w-100px h-100px bg-red text-#fff p-4px">
  atom css
</div>

一些比较流行的原子化 CSS 框架包括 TailwindCSS 和 UnoCSS。目前,TailwindCSS 已经成为事实上的原子化 CSS 标准。

CSS 使用方案的进化史

在详细介绍原子化 CSS 之前,我们先来回顾一下 CSS 使用方案的进化史:

1. 内联样式 (Inline Styles)

最原始的 CSS 使用方式。

优点:

  • 简单直接,容易理解。
  • 适用于快速试验和简单的样式需求。

缺点:

  • 无法复用样式,导致代码冗余。
  • 样式权重高,不利于动态修改。
  • 难以维护和管理,尤其是在大项目中。
<div
  style={{
    backgroundColor: "red",
    color: "#fff",
    padding: "10px 20px",
    borderRadius: "10px",
    fontSize: "20px",
    cursor: "pointer"
  }}
>
  button
</div>

2. 传统 Class + BEM 命名

BEM(Block, Element, Modifier)是一种命名约定,旨在解决 CSS 的命名冲突和复用问题。

优点:

  • 结构清晰,模块化强。
  • 提高样式的复用性。
  • 减少命名冲突,增强代码的可维护性。

缺点:

  • 命名较为复杂,学习成本较高。
  • 文件和代码量较大,可能显得冗长。
  • 重名class可能会导致样式覆盖问题(缺少模块化)。
<div class="button button--primary button__icon"></div>

3. CSS Modules

CSS Modules 是一种模块化的 CSS 方案,默认将样式局部化,避免全局命名冲突。也是目前比较流行的css解决方案。

优点:

  • 样式局部化,避免全局污染。
  • 可以使用 CSS 原生语法,易于上手。
  • 支持 CSS 预处理器。

缺点:

  • 需要构建工具的支持。
  • class样式书写较为繁琐。
  • 生产环境下的 class 名称会增加随机字符,不利于调试。同时常规的 class 选择器无法使用。
/* styles.module.css */
.button {
  background-color: blue;
}
import styles from './styles.module.css';

function Button() {
  return <button className={styles.button}>Button</button>;
}

4. CSS-in-JS

CSS-in-JS 是一种基于 JavaScript 和 CSS 的样式解决方案,利用标签模板字面量创建组件级别的样式。曾经是 React 社区中最流行的 CSS-in-JS 解决方案之一,但是现在已经逐渐被社区抛弃。

优点:

  • 样式与组件高度耦合,提升可维护性。
  • 动态样式支持强,可以使用 JavaScript 变量和逻辑。
  • 自动处理前缀和作用域,简化样式编写。

缺点:

  • 性能太差,会导致页面加载速度变慢。
  • 样式在组件内定义,可能导致文件过大。
  • 动态生成的 class 名称不利于调试。
  • 有一定的学习成本。
import styled from 'styled-components';

const Button = styled.button`
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border-radius: 10px;
  font-size: 20px;
  cursor: pointer;
`;

function App() {
  return <Button>Button</Button>;
}

5. Scoped CSS

Scoped CSS 是一种将 CSS 样式作用范围限定在特定组件或模块内的技术,常见于 Vue.js 中。

优点:

  • 样式局部化,避免全局污染。
  • 适用于单文件组件,结构清晰。
  • 与传统 class 语法书写一致,易于上手。

缺点:

  • 需要框架支持,目前仅流行于 Vue、Svelte等少数框架。
  • 样式共享和复用较为复杂。
<template>
  <div class="button">Button</div>
</template>

<style scoped>
.button {
  background-color: blue;
  color: white;
  padding: 10px 20px;
  border-radius: 10px;
  font-size: 20px;
  cursor: pointer;
}
</style>

原子化css

原子化css是一种css的编写方式,它将css样式拆分成最小的单元,然后通过组合这些单元来构建样式。这种方式的好处是可以提高样式的复用性,减少代码冗余,提高开发效率。

写一个原子化css的例子

优点

原子化css最大的优点就是提效。

传统的样式开发流程一般是:新建样式文件 → 起一个class名称 → 编写css代码 → 导入样式文件 → 在div上填写class名称。

原子化css的流程是这样:在div上填写class名称。

在维护方面,虽然原子化css也简化了传统css的查找流程,但是也带来了一些新的问题,那就是当class过多时,你想要通过控制台或者代码快速定位到目标class名,也不是一件容易的事,所以我认为在维护方面不算是有大的提升,甚至是带来了一些小问题。

> 解决了css私有化的问题

由于原子化css本来就是全局样式,所以根本上解决了css私有化的问题,因为不再需要私有了。这样就不用担心样式覆盖的问题,也不用担心样式冲突的问题。

> 降低命名负担,减少了嵌套命名

在传统的css编写方式中,我们需要为每个样式起一个名字,这样就会导致样式的命名负担,还要时刻担心重名class覆盖问题。而原子化css就不需要为每个样式起一个名字,从而降低了命名负担。同时减少了嵌套命名,例如BEM命名,提高了开发效率。

> 样式复用性高,减少代码冗余

原子化css虽然增加了class名的数量,但是通过组合这些样式,可以实现样式的复用,减少代码冗余。因为在这种模式下,你能预见到css文件的最大总量是固定的,同时框架本身还有 tree-shaking 的功能,可以去除没有使用的样式。要知道,你的网页加载越快,留存的用户就会越多。

> 与其他样式方案不冲突,可以结合使用

原子化css优秀的一点就是,他跟其他样式方案不是互斥的,这意味着你可以无缝的将原子化css结合到你的项目当中。

> 支持pxtorem、pxtovw等插件,完美兼容移动端

目前原子化框架和postcss的兼容性较好,可以通过postcss将px转换成rem等。完美适合移动端项目使用

> 有利于团队协作

原子化css的样式是固定的,不会因为不同的开发人员而有所不同,这样就可以减少团队协作中的样式冲突问题。 同时,当我们接手多个项目时,可以最大限度的降低样式方面维护难度。

> 开发时不用在html和css之间来回切换

同样提高了开发效率。而且使用原子化css根本就不需要写css,也就不存在css文件了,只需要在html中添加class名即可。

利用这个特性,我们还可以做一些插件:例如蓝湖插件,一键生成 原子化class 名,方便开发人员使用。

缺点

> 有一定的上手成本

因为目前流行的原子化css框架,提供的class名虽然是大部分基于css属性的,但是也有一些不是,这样就需要开发人员去记忆这些class名,这样就增加了一定的上手成本。

> 复杂样式类名太多难以阅读和维护

原子化css虽然提高了样式的复用性,但是也增加了class名的数量,这样就导致了class名太多,难以阅读和维护。例如:

<button
  class="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600 text-sm text-white font-mono font-light py-2 px-4 border-2 rounded border-blue-200"
>
  Button
</button>

解决方案有一些,但是目前都不够完美。

<!-- Attr模式  tailwindcss目前不支持,而且复杂的样式还是会冗长难读 -->
<button
  bg="blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600"
  text="sm white"
  font="mono light"
  p="y-2 x-4"
  border="2 rounded blue-200"
>
  Button
</button>

<!-- @apply方式,将多个原子类都继承到一个class下。但是这样又还不如直接写样式来的痛快 -->
<template>
  <button class="btn-primary"></button>
</template>
<style>
  .btn-primary {
    @apply blue-400 hover:blue-500 dark:blue-500 dark:hover:blue-600 text-sm text-white font-mono font-light py-2 px-4 border-2 rounded border-blue-200;
  }
</style>

个人使用感受

在我个人使用的过程中,也有一些小的吐槽点,之所以跟上面的缺点分开,是因为我觉得可能在别人那里这并不算是缺点。

存在一些非语义化的class

在目前主流的原子化css框架上都存在一些非语义化的class,比如m-1p-10等。很明显,m和p我知道是margin和padding,但是后面的数字的含义可能会让我感到疑惑。

在tailwindcss中,m-1的意思是margin: 0.25rem;。但是m-0的意思我们能猜到,是margin:0。在浏览器的规则里,等价于margin:0px。单位是px,所以我在一开始用的时候下意识以为m-1margin:1px。其次,rem的单位我们都知道,它并不是一个固定单位,它受到根元素的font-size的影响,因此这种语法我是很排斥使用的。

写法过于灵活

前面我们提到了,除了常规的class写法以外,还存在属性(attr)写法、继承(apply)写法等,假如一个团队里出现了多种写法,那这不仅没有提高协同效率,反而还带来了维护成本。就像jsx一样,带来了灵活性的同时,也让接手别人的react项目的开发人员抓狂。

可以自定义原子类

没错,原子化框架是允许你自己编写自己的原子类的。其实作为框架来说我觉得这没问题,这给开发人员带来了极大的自由度。但是,在开发时我们有多喜欢自由,那在维护时你就会有多憎恨自由。自由度是一把双刃剑。而我认为原子类应该是为了降低开发和维护难度而存在的,因此不应该过度自由。

如何正确的使用原子类css

在我日常的使用过程中,其实我发现使用原子类的基础功能已经可以满足日常90%的场景了。剩下的10%可以结合以前的class方案来去解决。至于前面提到的一些非标准化的原子类写法,从维护的角度还是尽量不要使用了。