随着 Facebook 和 Twitter 最近的产品部署,我认为一个新的趋势正在缓慢增长:Atomic CSS-in-JS。 在这篇文章中,我们将看到什么是Atomic CSS(原子 CSS),它如何与 Tailwind CSS 这种实用工具优先的样式库联系起来,目前很多大公司在 React 代码仓库中使用它们。 由于我不是这方面的专家,所以我不会去深入探讨它的利弊。我只是希望能帮助你了解它的大致内容。 先抛出一个令人开心的结论,新的 CSS 编写和构建方式让 Facebook 的主页减少了 80% 的 CSS 体积。 什么是原子 CSS? 你可能听说过各种 CSS 方法,如 BEM, OOCSS… <button class="button button--state-danger">Danger button</button> 现在,人们真的很喜欢 Tailwind CSS[1] 和它的 实用工具优先(utility-first)[2] 的概念。这与 Functional CSS 和 Tachyon[3] 这个库的理念非常接近。 <button 用海量的实用工具类(utility classes)组成的样式表,让我们可以在网页里大显身手。 原子 CSS 就像是实用工具优先(utility-first)CSS 的一个极端版本: 所有 CSS 类都有一个唯一的 CSS 规则。原子 CSS 最初是由 Thierry Koblentz (Yahoo!)在 2013 年挑战 CSS 最佳实践[4]时使用的。 /* 原子 CSS */ 使用实用工具/原子 CSS,我们可以把结构层和表示层结合起来:当我们需要改变按钮颜色时,我们直接修改 HTML,而不是 CSS! 这种紧密耦合在现代 CSS-in-JS 的 React 代码库中也得到了承认,但似乎 是 CSS 世界里最先对传统的关注点分离有一些异议。 CSS 权重也不是什么问题,因为我们使用的是最简单的类选择器。 我们现在通过 html 标签来添加样式,发现了一些有趣的事儿: 我们增加新功能的时候,样式表的增长减缓了。 我们可以到处移动 html 标签,并且能确保样式也同样生效。 我们可以删除新特性,并且确保样式也同时被删掉了。 可以肯定的缺点是,html 有点臃肿。对于服务器渲染的 web 应用程序来说可能是个缺点,但是类名中的高冗余使得 gzip 可以压缩得很好。同时它可以很好地处理之前重复的 css 规则。 一旦你的实用工具/原子 CSS 准备好了,它将不会有太大的变化或增长。可以更有效地缓存它(你可以将它附加到 vendor.css 中,重新部署的时候它也不会失效)。它还具有相当好的可移植性,可以在任意其他应用程序中使用。 实用工具/原子 CSS 的限制 实用工具/原子 CSS 看起来很有趣,但它们也带来了一些挑战。 人们通常手工编写实用工具/原子 CSS,精心制定命名约定。但是很难保证这个约定易于使用、保持一致性,而且不会随着时间的推移而变得臃肿。 这个 CSS 可以团队协作开发并保持一致性吗?它受巴士因子[5]的影响吗? 巴士系数是软件开发中关于软件专案成员之间讯息与能力集中、未被共享的衡量指标,也有些人称作“货车因子”、“卡车因子”(lottery factor/truck factor)。一个专案或计划至少失去若干关键成员的参与(“被巴士撞了”,指代职业和生活方式变动、婚育、意外伤亡等任意导致缺席的缘由)即导致专案陷入混乱、瘫痪而无法存续时,这些成员的数量即为巴士系数。 你还需要预先开发好一个不错的实用工具/原子样式表,然后才能开始开发新功能。 如果实用工具/原子 CSS 是由别人制作的,你将不得不首先学习类命名约定(即使你知道 CSS 的一切)。这种约定是有主观性的,很可能你不喜欢它。 有时,你需要一些额外的 CSS,而实用工具/原子 CSS 并不提供这些 CSS。没有约定好的方法来提供这些一次性样式。 Tailwind 赶来支援 Tailwind 使用的方法是非常便捷的,并且解决了上述一些问题。 它通过 Utility-First 的理念来解决 CSS 的一些缺点,通过抽象出一组类名 -> 原子功能的集合,来避免你为每个 div 都写一个专有的 class,然后整个网站重复写很多重复的样式。 它并不是真的为所有网站提供一些唯一的实用工具 CSS,取而代之的是,它提供了一些公用的命名约定。通过一个配置文件[6],你可以为你的网站生成一套专属的实用工具 CSS。 ssh 注:这里原作者没有深入介绍,为什么说是一套命名约定呢而不是生成一些定死的 CSS 呢? 举几个例子让大家感受一些,Tailwind 提供了一套强大的构建系统,比如默认情况下它提供了一些响应式的断点值: // tailwind.config.js 你可以随时在配置文件中更改这些断点,比如你所需要的小屏幕 sm 可能指的是更小的 320px,那么你想要在小屏幕时候采用 flex 布局,还是照常写 sm:flex,遵循同样的约定,只是这个 sm 已经被你修改成适合于项目需求的值了。 在比如说,Tailwind 里的 spacing 掌管了 margin、padding、width 等各个属性里的代表空间占用的值,默认是采用了 rem 单位,当你在配置里这样覆写后: // tailwind.config.js 你再去写 h-6(height), m-2(margin), mb-4(margin-bottom),后面数字的意义就被你改变了。 也许从桌面端换到移动端项目,这个 6 代表的含义变成了 6rem,但是这套约定却深深的印在你的脑海里,成为你知识的一部分了。 Tailwind 的知识可以迁移到其他应用程序,即使它们使用的类名并不完全相同。这让我想起了 React 的「一次学习,到处编写」理念。 我看到一些用户反馈说,Tailwind 提供的类名能覆盖他们 90% - 95% 的需求。这个覆盖面似乎已经足够广了,并不需要经常写一次性的 CSS 了。 此时,你可能想知道为什么要使用原子 CSS 而不是 Tailwind CSS?强制执行原子 CSS 规则的一个规则,一个类名,有什么好处?你最终会得到更大的 html 标签和更烦人的命名约定吗?Tailwind 已经有了足够多的原子类了啊。 那么,我们是否应该放弃原子 CSS 的想法,而仅仅使用 Tailwind CSS? Tailwind 是一个优秀的解决方案,但仍然有一些问题没有解决: 需要学习一套主观的命名约定 CSS 规则插入顺序仍然很重要 未使用的规则可以轻松删除吗? 我们如何处理剩下的一次性样式? 与 Tailwind 相比,手写原子 CSS 可能不是最方便的。 和 CSS-in-JS 比较 CSS-in-JS 和实用工具/原子 CSS 有密切关系。这两种方法都提倡使用标签进行样式化。以某种方式试图模仿内联样式,这让它们有了很多相似的特性(比如在移动某些功能的时候更有信心)。 全局命名空间 依赖 无用代码消除 代码压缩 共享常量 非确定性(Non-Deterministic)解析 隔离 实用工具/原子 CSS 也解决了其中的一些问题,但也确实没法解决所有问题(特别是样式的非确定性解析)。 如果它们有很多相似之处,那我们能否同时使用它们呢? 探索原子 CSS-in-JS 原子 CSS-in-JS 可以被视为是“自动化的原子 CSS”: 你不再需要创建一个 class 类名约定 通用样式和一次性样式的处理方式是一样的 能够提取页面所需要的的关键 CSS,并进行代码拆分 有机会修复 JS 中 CSS 规则插入顺序的问题 我想强调两个特定的解决方案,它们最近推动了两个大规模的原子 CSS-in-JS 的部署使用,来源于以下两个演讲。 React-Native-Web at Twitter (更多细节在Nicolas Gallagher 的演讲[8])。 Stylex at Facebook (更多细节在Frank Yan 的演讲[9])。 也可以看看这些库: Styletron Fela Style-Sheet cxs otion css-zero ui-box style9 stitches catom React-Native-Web React-Native-Web 是一个非常有趣的库,让浏览器也可以渲染 React-Native 原语。不过我们这里并不讨论跨平台开发(演讲里有更多细节)。 作为 web 开发人员,你只需要理解 React-Native-Web 是一个常规的 CSS-in-JS 库,它自带一些原始的 React 组件。所有你写 View 组件的地方,都可以用 div 替换。 React-Native-Web 的作者是 Nicolas Gallagher,他致力于开发 Twitter 移动版。他们逐渐把它部署到移动设备上,不太确定具体时间,大概在 2017/2018 年左右。 从那以后,很多公司都在用它(美国职业足球大联盟、Flipkart、Uber、纽约时报……),但最重要的一次部署,则是由 Paul Armstrong 领导的团队在 2019 年推出的新的 Twitter 桌面应用。 |
|