为什么我觉得 Tailwind 真香

swyx 原作,授权 New Frontend 翻译。

我不是 Tailwind 托。我是一个过来人——我是后来才改变对 Tailwind 的看法的,虽然意识到 Tailwind 有许多妥协,我仍是 Tailwind 的快乐用户。对心有疑虑的人而言,「跳反者」常常比始终如一的信徒更有说服力。所以,我在这里写下自己的看法。如果你对 Tailwind 持开放态度,希望这篇文章对你能有一定的参考价值。

Adam Wathan 曾经问过:「直到实际使用 Tailwind 做项目之前,你是不是一直觉得 Tailwind 很糟糕?」

我当时是这么回复的:

我曾向 @samselikoff 抱怨,Tailwind 带来一坨坨丑陋难读的类名。无运行时开销的 CSS-in-JS 可以做到更多,学习曲线却更低。

我有两个地方错了:Tailwind 学起来比我想的容易,CSS-in-JS 的灵活性可能是负面的

基于 Tailwind 实现一些项目后(包括我的个人网站和我写的书的网站),我觉得我应该列出自己喜欢 Tailwind 的方面。因为 Tailwind 是占统治地位的工具 CSS 框架,我也只用过这一个工具 CSS 框架,所以下文不会区分 Tailwind 的优势和工具 CSS 本身的优势(不过这里有一份工具 CSS 框架的列表)。

TL;DR

  • 「系统」值减少了神奇数字: 少用硬编码值,提升一致性。
  • 浏览器内的响应式设计: 在浏览器里进行原型设计,复制粘贴到代码仓库,使用一致的系统值。
  • 内联样式便于调整: 不再依赖层叠样式,让代码更容易删除和移动。
  • 内联样式减少命名: 解决了计算机科学两大难题之一后,项目交付更快!
  • 零 JS、次线性扩展 CSS: 以 O(log N) 扩展,而非以 O(N) 扩展。
  • 工具优先,并非工具唯一: 遵循最小表达力原则,只在必要时使用 CSS-in-JS。

兼听则明,偏信则暗,所以文末我也列举了流行的反对 Tailwind 的观点,你可以点击链接查看他们的论据。

讨论工具优先的经典文章

在阅读我的观点之前,你也许希望先查阅最有影响力的关于这场 CSS 「工具类」革命的文献:

我深受这些人和其他一些人的影响,所以如果我在下文拙劣地重复了其中一些观点,那么责任完全在我。至少我首先给出了权威文献。

「系统」值减少了神奇数字

CSS 极其灵活,这既让它非常强大,也让你有很多机会引火烧身。有所限制十分必要,否则代码仓库会充满各种「神奇数字」。

Chris Coyier 说过,CSS 中的神奇数字很糟糕。他将「神奇数字」定义为「在某些情况下可以工作,但很脆弱,情况改变时容易奔溃的数字」。不过,老实说,任何硬编码的数字,诸如边距中的 px、媒体查询、颜色变体,一旦项目大了就很难管理好。打破规则以便尽快修复问题的诱惑是实际存在的,设计需求变更时重构难度又过高。如果你独自开发项目,没什么可以强制你使用设计良好、系统性的数字,这可能导致丑陋的设计。

显然,解决方案是只从预先选定的数值中选择,我称其为「系统」(注意我不把它叫做「设计体系」,关于这点我不想争论)。Tailwind 自带一套默认的字体和色彩值系统。你当然可以基于 CSS 变量自己做一套,但相应的语法很冗长,而且你仍然需要想类名和变量名,最终得到的一套难以在不同项目间通用的定制系统,多半还缺乏文档。或许你还要请 Steve Schoger 来给团队培训一下如何设计一套良好的默认系统。

注:jxnblk 做的 Styled SystemTheme UI 是 CSS-in-JS 下的替代方案。

浏览器内的响应式设计

兼职设计的开发者更在意这一点:最佳的开发流程是在 localhost 上预览站点,在浏览器内直接调整直到得到满意的效果,然后直接复制粘贴改动到代码仓库。让我们把这一工作流程称为在浏览器内设计

如果你需要这样的工作流程,那就不能使用 React 的内联样式了(那需要写对象语法)。不过让我们假定你使用某种「书写真正的 CSS」™解决方案,比如 Styled-Component,或者 Vue、Svelte 的局部生效样式(scoped styles),让在浏览器内做设计成为可能。那么,Tailwind 能提供什么呢?

  1. 在浏览器里进行原型开发时,你可以直接从预设的「系统」值(上文解释过)中选取需要使用的值。
  2. 你也可以浏览器内进行响应式设计和伪类设计,例如,在不同的断点、悬浮时、获得焦点时应用样式,比如,在一个链接那里直接附加内联代码,text-green-400 hover:text-green-300 md:text-blue-400

我在最近的一个视频中演示了一下:

在浏览器内设计不太符合 Bret Victor 提出的从原则出发的设计,但至少缩短了认知距离,让你能更方便地在上下文环境中进行各种尝试。Tailwind 甚至让你可以在尝试的时候往内联代码中加过渡动画效果。这一点被大大低估了——如果开发者能更方便地在原型中加入过渡和动画效果,那么最终交付的应用中就更可能带上生动的视觉效果。

内联样式便于调整

大量用于生产环境的 CSS 只是附加样式。这是因为 CSS 和受其影响的标记语言之间的认知距离常常很远——有时在不同的文件夹里,有时在不同的文件下,有时虽然在同一文件内但隔了许多行。除此之外,别忘了 CSS 还有层叠机制,你需要在大脑中把每个元素匹配到的每一条规则都过一遍。

很快你就不敢打开代码仓库了!开发速度下降,渐渐陷入死胡同,除了重写别无他途。

从设计上而言,CSS 很容易扩展。只需加上更具体的规则!但删除就不那么容易了。这导致复杂度的增加,Rich Hickey 在这方面的阐述是最好的(因为位置在 CSS 里很重要,你需要记住所有位置)。用卡片搭房子很容易,但抽掉一张可能导致整栋大厦坍塌。直到你检查视觉回退问题或者在头脑里模拟浏览器为止,你都不会知道这一点

你可以使用一些工具(CSS 模块、静态 CSS in JS,Vue 或 Svelte 的局部生效样式)或者命名规范(BEM)来控制这一问题,但这仅仅缩短了认知距离,而没能消除它。内联样式是完全消除这种「神出鬼没」的唯一选择。内联样式便于调整

「银河大脑」(Galaxy Brain)时间:Tailwind 向开发者提供了内联样式带来的速度优势,但消除了它的劣势。

替代方案:Emotion 的 css 属性和 styled-jsx 之类的解决方案同样提供了内联样式的益处,不过它们有 CSS in JS 的劣势。

DECOMPLECT

内联样式减少命名

命名是一个已知的困难问题。争吵一个类应该叫什么浪费了我们大量的时间。使用 Styled-Components 时常常需要写很多中间样式组件,这些都需要命名。BEM 则将一个命名问题替换为三个半命名问题(我曾经收到一些关于不应使用 --__ 的 PR,完全是浪费时间)。我比较好奇每年因为争吵命名问题浪费了多少开发人时,估计是百万级的。

工具 CSS 显著减少了代码仓库中需要命名的地方的数量,也许更重要的是,减少了我们需要独自发明和记忆的名称的数量。这看起来微不足道,但如果你曾经接触过需要独自发明和记忆大量名称的项目,你就不会这么想了。你愿意为了消除这一个已知的已知问题付出多大的代价?我可没开玩笑——这是一个值得讨论的话题。对机器来说,名称无足轻重,但对人类而言,至关重要。

译者注:「已知的已知问题」致敬了前美国国防部长拉姆斯菲尔德在回答记者关于伊拉克政府拥有大规模杀伤性武器的证据的提问时的名言:「据我们所知,有『已知的已知』,有些事,我们知道我们知道,有『已知的未知』,也就是说,有些事,我们现在知道我们不知道。但是,同样存在『未知的未知』——有些事,我们不知道我们不知道。」

缺点是你需要学习工具 CSS 框架的名称。不看文档的话,很难明白 -mb-5space-x-reverse 是什么意思。区别在于传统的 CSS 代码需要了解每个项目中的名称,而 Tailwind 只需学习一次,就可以在所有基于 Tailwind 的项目中应用。没错,你可以尝试开发自己的工具 CSS,但 Tailwind 的命名多半比你自己想出来的要更妥切。

替代方案:Emotion 的 css 属性和 styled-jsx 同样让你可以省略命名。

零 JS、次线性扩展 CSS

关于 CSS in JS 在性能方面的妥协和如何缓解性能问题,已经有很多文章讨论了。如果你想了解详情,可以看下我 关于 React 新进展的讲座。概括来说,这是一个激烈争论的话题,正反两方都有富有激情和智慧的成员。但我们都同意,最终发布到客户端的 JS 越少越好,也都同意发布 1 kb JS 对性能造成的影响比 1 kb CSS 大得多。这些都很好理解。

我在这里想要指出的一点是对许多 CSS 和 CSS in JS 解决方案而言,CSS 文件大小随着应用中组件的数量而线性增长。因为每个 CSS 声明语句的作用域对应相应的标识符,所以每个需要应用样式的地方都得重复写一遍。这就是我们在之前的一个项目中最终把 font-weight: bold 重复了超过 50 遍的原因。在一个地方加一条语句没什么,但在很多地方加上同样的语句,就积累成了问题。

我们过去的站点加载了超过 400 KB 的压缩过的 CSS(压缩前是 2 MB)……刚开始并没有那么多 CSS;都是逐渐增加的,移除 CSS 声明语句的情况则非常罕见。出现这样的现象,部分原因是每个新特性都意味着增加新的 CSS。

—— Facebook 工程部门

拆分代码可以延缓这一问题,但随着人们实现和发布各种曲折的样式逻辑,CSS 会再次失控(如果 CSS 代码以仅附加的方式支持新特性,这个问题就更突出了)。

这一问题的解决方案(可能有些争议)是发布「原子」CSS,这样 CSS 就以 O(log N) 而不是 O(N) 的级别增长(N 是组件数)。Facebook 未发布的 stylex 库支持编写 CSS in JS 代码,然后自动生成原子 CSS。不过你也可以选择手写原子 CSS,Tailwind 这类工具 CSS 框架引导你这么做。

公平起见,我把这一点放在后面,因为就大多数应用的规模而言,这不至于成为问题,而且 CSS 代码还会经过 gzip 压缩。然而,所有优化都是过早优化,直到不再是过早优化的那一天。

工具优先,并非工具唯一

获得上文提及的那些收益之后,你仍然可以在需要的时候使用其他解决方案。例如,任何方案都不如 CSS in JS 强大,可以使用任意 JavaScript 语句动态改变媒体查询值和整个规则集。

然而,实际项目中,确实需要这么强大的能力的场景是有限的,静态样式也使用 CSS in JS 的成本太高了(比如,增加 JS 代码量)。工具优先是符合最小表达力原则的做法。

非 CSS in JS 方案通常也更容易调试。当然这里的「容易」比较主观。但 CSS in JS 方案出问题时,我常常需要去翻文档、GitHub 项目上的 issue 列表、node_modules,这些牦牛剃毛任务分散了我的注意力,让我无法集中精力解决实际问题。相反,Tailwind 项目出问题的时候,我只需要检查是否生成了符合预期的类名。Tailwind 在这方面不那么灵活多变是有理由的,条件允许的情况下,大多数时候,你都应该使用便于调试的方案。

译者注:牦牛剃毛任务指为了完成手头的任务,需要先完成一系列任务,这些任务可能和原任务不直接相关,但不解决的话原任务就无法完成,而且这些任务还可能引出其他需要完成的任务。

劣势

Tailwind 很完美?当然不是。不过是优势盖过了劣势:

  • 配置 Tailwind 需要折腾构建工具。不过现在已经比以前方便许多,而且其他方案要达到类似的性能,也需要配置构建工具,但有些人觉得这不可接受。
  • Tailwind 的 API 很多,而且还在不断增加。这是可以理解的(它需要把所有 CSS 的功能映射到 HTML!),但学习和跟进起来很累。最终结果是你在开始阶段付出了一些学习成本,希望能提高长期的开发效率。不错的结果,但算不上全胜。
  • 一串串类名,看起来很冗长。如果能支持 md:hover:(text-green-300 underline border-5) 这样的捷径就会好很多,但这又会让 API 变得更复杂。(来自未来的更新:oceanwind 实现了这一捷径)这也许可以用 @apply 或者 tailwindcss-typography 解决。不过总体而言,我觉得能够方便地修改内联代码比一些肤浅的美学考量更重要。网站的最终用户肯定不会在意这些。
  • CSS 抽象泄漏 —— Tailwind 让你像使用内联样式一样使用类,但在某个关键点上,类和内联样式不一样 —— 出现冲突时的行为。例如,m-4 m-2m-4 的优先级更高。Tailwind 生成的类的顺序对你来说是不可见的,但它们的顺序却会影响样式。这属于抽象泄漏。如果你正尝试为一套内部设计系统构建可重用的组件,那么这是不采用 Tailwind 的一个理由。这种场景下,可以看看 Chakra UI。
  • Tailwind 项目由 Tailwind Labs 主导 —— 当然,目前而言,作为 Tailwind 项目的创始人,Tailwind Labs 将项目管理得很好,但和 CSSWG 这样有目共睹的平衡兼顾各方意见的组织还是有所不同。社区和一切 BDFL 的关系都一样,直到彼此不合前,关系都很好。
  • (小问题)VS Code 插件仍然不够健壮 —— 需要按下空格才能激活,常常不工作。不过 Brad Cornes 正解决这个问题。

结论:金发姑娘风格的方案

综上所述,我觉得选择 Tailwind 更多的是个人偏好,算不上客观的标准答案。样式解决方案的光谱很宽,从极有主张的到无主张的。我最近曾发推总结我的看法

  • 预制组件库限制太多,原生 CSS 限制太少。
  • CSS in JS 太重,内联 CSS 太弱。
  • 我们希望能有系统性的设计限制,但我们着手设计的系统却笨手笨脚的(而且受限于框架)。
  • Ben Holmes 说「对于想要自由的设计师和想要结构化的开发者来说,这是一个坚实的中间地带」。

Tailwind 适合那些想要「不太冷也不太热的」样式解决方案的人。

这是金发姑娘风格的方案。

Goldilocks

译者注:金发姑娘是罗伯特·骚塞的童话故事《三只小熊》中的人物,觉得不太冷也不太热的粥最好、不太大也不太小的床和椅子最舒适。

附录:不同观点

对一些反对意见的回应(编辑于 2021 年 1 月)

  • HTML 超级丑! 相比开发速度和交付更好的用户体验,代码美学应该往后靠。
  • 明明存在 CSS 变量! 没错,但内联样式还是会打断工作流。margin-bottom: var(--spacing-8)class="mb-8" 可不等价。
  • 明明存在 web 组件! 你当然可以同时使用两者,不过你的应用到底有多少是基于 web 组件的?对于那些主体部分并不基于 web 组件的应用而言,Tailwind 是一个非常棒的选择。
  • 我不喜欢 @apply 很好,你本来也不该过多地使用它们。另外将 @apply 降为实质性的 CSS 是微不足道的任务。

题图 Meduana

评论

Loading comments ...