原子 CSS-in-JS
什么是原子CSS?
实用/原子 CSS 的局限性
顺风来救援
与 CSS-in-JS 的比较
输入原子 CSS-in-JS
你仍然更喜欢 Tailwind 吗?
结论
本文已在其他平台转载。
从 Facebook 和 Twitter 最近的生产部署来看,我认为一种新的趋势正在慢慢兴起:原子 CSS-in-JS。
在这篇文章中,我们将了解什么是原子 CSS,它与 TailwindCSS 等函数式/实用优先 CSS 的关系,以及大型开发者如何在他们的现代 React 代码库中采用它。
由于我并非这方面的专家,所以请不要期待我会深入探讨它的优缺点。我只是希望您能对它有个大致的了解。
注意:原子 CSS 与原子设计并没有真正的关系。
什么是原子CSS?
你可能听说过各种 CSS 方法论,例如 BEM、OOCSS 等……
<button class="button button--state-danger">
Danger button
</button>
如今,人们非常喜欢Tailwind CSS及其实用至上的理念。它与 Functional CSS 和Tachyon非常接近。
<button class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded">
Button
</button>
只需一个包含大量实用类的样式表,就能实现很多功能。
原子 CSS 就像是实用优先 CSS 的一个极端版本:所有 CSS 类都对应一条唯一的 CSS 规则。它最早由 Thierry Koblentz(雅虎!)在 2013 年发表的《挑战 CSS 最佳实践》Atomic CSS一书中提出。
/* Atomic CSS */
.bw-2x {
border-width: 2px;
}
.bss {
border-style: solid;
}
.sans {
font-style: sans-serif;
}
.p-1x {
padding: 10px;
}
/* Not atomic, because the class contains 2 rules */
.p-1x-sans {
padding: 10px;
font-style: sans-serif;
}
使用实用/原子 CSS,我们承认结构层和表现层耦合是可以接受的:当我们需要更改按钮颜色时,我们修改 HTML,而不是 CSS。
这种紧密耦合在现代的 CSS-in-JS React 代码库中也得到了认可,但 CSS 界似乎更早意识到 Web 的“关注点分离”并不适用。
由于我们使用简单的类选择器,因此特异性也不是什么问题。
我们现在通过标记进行样式设置,它具有几个有趣的特性:
- 随着我们添加新功能,样式表的大小会逐渐减小。
- 我们可以移动标记,样式也会同时移动。
- 我们可以移除某些功能,并确保同时移除相关的样式。
没错,HTML文件确实有点臃肿。
这对于服务器端渲染的Web应用来说可能是一个问题,但类名中的大量冗余可以用gzip很好地压缩,就像它之前对CSS文件中重复的CSS规则有效一样。
你不需要在所有情况下都使用实用/原子 CSS,只需使用最常见的样式模式即可。
一旦您的实用/原子 CSS 准备就绪,它就不会发生太大变化或增长。
您可以更积极地对其进行缓存(vendor.css例如,您可以将其添加到 `<head>` 标签中,并期望它在应用程序重新部署后不会失效)。
它也具有良好的可移植性,您可以在其他应用程序中使用它。
实用/原子 CSS 的局限性
实用/原子 CSS 看起来很有意思,但它们也带来了一些挑战。
人们通常手工编写实用/原子 CSS,并精心设计命名规范。
要确保这种规范易于使用、保持一致性,并且不会随着时间的推移而变得臃肿,可能比较困难。
多人协作开发这些 CSS 能否保持其一致性?
它是否会受到“公交车因素”的影响?
你还需要预先创建一个好的实用/原子样式表,然后才能迭代开发将要使用它的功能。
如果实用/原子 CSS 是由其他人编写的,即使你精通 CSS,也必须先学习其类命名规范。
这种规范带有主观性,你可能并不喜欢。
有时,你需要一些额外的 CSS,而这些 CSS 并非由你的实用/原子 CSS 提供。
目前还没有统一的方法来提供这些额外的特殊样式。
顺风来救援
Tailwind 的方法非常方便,可以解决其中的一些问题。
它实际上并没有为所有网站提供统一的实用 CSS 文件,而只是提供了一个共享的作用域和命名规范。通过配置文件,您可以生成自定义的实用 CSS 文件。
即使其他应用程序使用的类名不完全相同,Tailwind 的知识也可以迁移到其他应用程序中。这让我想起了 React 的“一次学习,到处编写”理念。
我看到有人反映 Tailwind 类可以满足他们 90% 到 95% 的需求。看来它的覆盖范围足够大,我们通常不需要使用一次性的自定义样式。
此时你可能会想why use atomic CSS instead of Tailwind?:
强制执行原子 CSS 规则有什么好处呢1 rule, 1 class?
最终只会导致 HTML 标记更大,命名规则也更不方便?
Tailwind 本身已经有很多原子类了。
那么,我们是否应该放弃原子 CSS 的理念,而直接使用 Tailwind CSS 呢?
Tailwind 是一个很棒的解决方案,但仍有一些问题尚未解决:
- 需要学习一种带有主观色彩的命名规则
- CSS 规则的插入顺序仍然很重要。
- 未使用的规则可以轻松删除吗?
- 剩下的那些独一无二的款式我们该如何处理?
与 Tailwind 相比,手写原子 CSS 可能不是最方便的。
与 CSS-in-JS 的比较
它与 CSS-in-JS 和实用/原子 CSS 有关联。这两种方法都提倡从标记中定义样式,试图模拟高性能的内联样式,这使得它们具有许多相似的特性(例如能够自信地移动元素)。
Christopher Chedeau对 React 生态系统中 CSS-in-JS 理念的传播做出了巨大贡献。他在多次 演讲中阐述了 CSS 存在的问题:
实用/原子 CSS 也能解决其中一些问题,但绝对不能解决所有问题(特别是样式的不确定性解析)。
如果它们有相似之处,我们能不能同时使用它们?
输入原子 CSS-in-JS
原子 CSS-in-JS 可以看作是“自动原子 CSS”:
- 你不再需要创建类命名约定了。
- 常见款式和一次性款式的处理方式相同。
- 能够提取页面的关键 CSS 并进行代码分割
- 有机会修复 JS 中 CSS 规则插入顺序的问题。
我并不了解目前所有 CSS-in-JS 库是否都支持原子 CSS。实际上,是否支持原子 CSS 是 CSS-in-JS 库的一个实现细节。这项支持可能会时有时无,甚至可以设为可选。
我将重点介绍两个具体的解决方案,这两个方案最近促成了两个大规模的原子级 CSS-in-JS 部署,并以两场演讲为参考:
- Twitter 上的 React-Native-Web(更多详情请参见Nicolas Gallagher 的演讲)
- Stylex 在 Facebook 上(更多详情请见Frank Yan 的演讲)
React-Native-Web
React-Native-Web 是一个非常有趣的库:它允许在 Web 上渲染 React-Native 的基本组件。我们这里并不是在讨论跨平台移动/Web 开发(更多详情请观看相关演讲)。
作为一名 Web 开发人员,您只需了解 React Native Web 是一个标准的 CSS-in-JS 库,它自带少量基本的 React 组件。
凡是看到 `<link>`标签的地方View,您都可以将其替换为 `<link>` 标签div,这样就很容易上手了。
React-Native-Web 由Nicolas Gallagher创建,最初用于 Twitter 移动应用开发。他们逐步将其部署到移动端,具体时间不详,但大概在 2017/2018 年左右。此后,其他公司(例如美国职业足球大联盟、Flipkart、Uber、《泰晤士报》等)也开始使用 React-Native-Web,但最重要的部署是Paul Armstrong
领导的团队在 2019 年推出的全新 Twitter 桌面应用。
Stylex
Stylex 是 Facebook 为 2020 年 Facebook 改版项目(目前处于 beta 测试阶段)开发的一个全新的 CSS-in-JS 库。他们似乎计划在未来某个时候将其开源,或许会使用不同的名称。
值得一提的是,React-Native-Web 的作者 Nicolas Gallagher 两年前被 Facebook 聘用。因此,Facebook 重复利用 React-Native-Web 的一些概念也就不足为奇了。
与 React-Native-Web 不同,Stylex 似乎并不专注于跨平台开发。
我目前掌握的信息都来自那次谈话 :) 我们还需要等待更多细节。
可扩展性
正如预期,采用原子化 CSS 后,Twitter 和 Facebook 的 CSS 代码量都大幅减少,现在呈对数增长趋势。不过,对于简单的应用程序来说,这需要付出一定的初始代价。
Facebook分享了具体数据:
- 他们之前的网站,光首页的 CSS 代码就高达 413Kb。
- 他们的新网站整个大小为 74Kb ,包括深色模式。
来源和输出
这两个库似乎具有相似且相当简单的 API,但考虑到我们对 Stylex 了解不多,很难说哪个更好。
值得一提的是,React-Native-Web 将扩展 CSS 简写语法,例如margin: 0。
生产检验
让我们来看看Twitter上的标记是什么样子的:
现在,让我们来看看新的Facebook:
很多人看到这个可能会感到震惊,但它确实有效,而且仍然易于使用。
在 Chrome 开发者工具中调整样式可能有点困难,但开发者工具可以提供帮助:
CSS 规则顺序
与手写实用/原子 CSS 不同,JS 库能够使样式不再依赖于 CSS 规则的插入顺序。
您可能知道,如果规则发生冲突,生效的并非类属性中最后一个类名,而是样式表中最后插入的规则。仅使用基于类的简单选择器即可解决优先级问题。
实际上,这些库会避免在同一元素上输出规则冲突的类。
它们确保标记中最后声明的样式始终生效。
“被覆盖的类”会被过滤掉,甚至不会进入 DOM 结构。
const styles = pseudoLib.create({
red: {color: "red"},
blue: {color: "blue"},
});
// That div only will have a single atomic class (not 2!), for the blue color
<div style={[styles.red, styles.blue]}>
Always blue!
</div>
// That div only will have a single atomic class (not 2!), for the red color
<div style={[styles.blue, styles.red]}>
Always red!
</div>
注意:这种可预测的行为只有通过使用最严格的原子 CSS 才能实现。
如果一个类有多个规则,并且只覆盖了其中一个规则,那么 CSS-in-JS 库将无法在不删除未被覆盖的规则的情况下过滤该类。
如果一个类只有一个简写规则,例如 `className` margin: 0,而其重写规则是 `className` marginTop: 10,则会出现同样的问题。简写语法 `className`margin: 0会被展开为 4 个不同的类,库能够更精细地过滤掉不应出现在 DOM 中的重写类。
你仍然更喜欢 Tailwind 吗?
一旦你掌握了所有 Tailwind 的命名规则,你就可以非常快速地编写 UI 代码。相比之下,像在 CSS-in-JS 中那样手动编写每一条 CSS 规则可能会显得效率低下。
在原子化的 CSS-in-JS 框架之上构建自己的抽象层完全没有问题。Styled -system或许能够运行一些支持原子 CSS 的 CSS-in-JS 库。如果你觉得这样做效率很高,甚至可以复用 Tailwind in JS 的命名约定。
让我们来看一段 Tailwind 代码:
<div className="absolute inset-0 p-4 bg-blue-500" />
现在,让我们来看一个我刚在谷歌上找到的随机解决方案( react-native-web-tailwindcss ):
import {t} from 'react-native-tailwindcss';
<View style={[t.absolute, t.inset0, t.p4, t.bgBlue500]} />
从生产力角度来看,这并没有太大区别。而且使用 TypeScript 可以避免拼写错误。
结论
对我来说,同时使用 Atomic CSS、CSS-in-JS 和 Tailwind 是合理的。
关于原子级 CSS-in-JS,我几乎要说的就这些了。
我从未在任何大型生产环境中使用过原子 CSS、原子 CSS-in-JS 或 Tailwind。我可能有些地方说得不对,欢迎在Twitter上指正。
我认为原子化的 CSS-in-JS 是 React 生态系统中值得关注的趋势,希望这篇文章能对你有所帮助。
由于我找不到任何关于原子化 CSS-in-JS 的文章,所以这篇文章主要是为了我自己。
我希望以后在博客文章中提到原子化 CSS-in-JS 时可以链接到这篇文章(我计划写更多关于 React Native Web 和跨平台开发的文章,敬请期待)。
感谢阅读。
如果你喜欢,请转发分享。
浏览器代码演示,或者在博客仓库中纠正我帖子中的拼写错误
文章来源:https://dev.to/sebastienlorber/atomic-css-in-js-4cdb













