百度360必应搜狗淘宝本站头条
当前位置:网站首页 > 技术文章 > 正文

比less/sass更便捷的新一代CSS编写方式

myzbx 2025-01-24 13:05 33 浏览

随着 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 和它的 实用工具优先(utility-first) 的概念。这与 Functional CSS 和 Tachyon 这个库的理念非常接近。

<button
  class="bg-blue-500 hover:bg-blue-700 text-white font-bold py-2 px-4 rounded"
>
  Button
</button>

用海量的实用工具类(utility classes)组成的样式表,让我们可以在网页里大显身手。

原子 CSS 就像是实用工具优先(utility-first)CSS 的一个极端版本: 所有 CSS 类都有一个唯一的 CSS 规则。原子 CSS 最初是由 Thierry Koblentz (Yahoo!)在 2013 年挑战 CSS 最佳实践时使用的。

/* 原子 CSS */
.bw-2x {
  border-width: 2px;
}
.bss {
  border-style: solid;
}
.sans {
  font-style: sans-serif;
}
.p-1x {
  padding: 10px;
}
/* 不是原子 CSS 因为这个类包含了两个规则 */
.p-1x-sans {
  padding: 10px;
  font-style: sans-serif;
}

使用实用工具/原子 CSS,我们可以把结构层和表示层结合起来:当我们需要改变按钮颜色时,我们直接修改 HTML,而不是 CSS!

这种紧密耦合在现代 CSS-in-JS 的 React 代码库中也得到了承认,但似乎 是 CSS 世界里最先对传统的关注点分离有一些异议。

CSS 权重也不是什么问题,因为我们使用的是最简单的类选择器。

我们现在通过 html 标签来添加样式,发现了一些有趣的事儿:

  • 我们增加新功能的时候,样式表的增长减缓了。
  • 我们可以到处移动 html 标签,并且能确保样式也同样生效。
  • 我们可以删除新特性,并且确保样式也同时被删掉了。
  • 可以肯定的缺点是,html 有点臃肿。对于服务器渲染的 web 应用程序来说可能是个缺点,但是类名中的高冗余使得 gzip 可以压缩得很好。同时它可以很好地处理之前重复的 css 规则。

    一旦你的实用工具/原子 CSS 准备好了,它将不会有太大的变化或增长。可以更有效地缓存它(你可以将它附加到 vendor.css 中,重新部署的时候它也不会失效)。它还具有相当好的可移植性,可以在任意其他应用程序中使用。

    实用工具/原子 CSS 的限制

    实用工具/原子 CSS 看起来很有趣,但它们也带来了一些挑战。

    人们通常手工编写实用工具/原子 CSS,精心制定命名约定。但是很难保证这个约定易于使用、保持一致性,而且不会随着时间的推移而变得臃肿。

    这个 CSS 可以团队协作开发并保持一致性吗?它受巴士因子的影响吗?

    巴士系数是软件开发中关于软件专案成员之间讯息与能力集中、未被共享的衡量指标,也有些人称作“货车因子”、“卡车因子”(lottery factor/truck factor)。一个专案或计划至少失去若干关键成员的参与(“被巴士撞了”,指代职业和生活方式变动、婚育、意外伤亡等任意导致缺席的缘由)即导致专案陷入混乱、瘫痪而无法存续时,这些成员的数量即为巴士系数。

    你还需要预先开发好一个不错的实用工具/原子样式表,然后才能开始开发新功能。

    如果实用工具/原子 CSS 是由别人制作的,你将不得不首先学习类命名约定(即使你知道 CSS 的一切)。这种约定是有主观性的,很可能你不喜欢它。

    有时,你需要一些额外的 CSS,而实用工具/原子 CSS 并不提供这些 CSS。没有约定好的方法来提供这些一次性样式。

    Tailwind 赶来支援

    Tailwind 使用的方法是非常便捷的,并且解决了上述一些问题。

    它通过 Utility-First 的理念来解决 CSS 的一些缺点,通过抽象出一组类名 -> 原子功能的集合,来避免你为每个 div 都写一个专有的 class,然后整个网站重复写很多重复的样式。

    传统卡片样式写法:

    Tailwind 卡片样式写法:

    它并不是真的为所有网站提供一些唯一的实用工具 CSS,取而代之的是,它提供了一些公用的命名约定。通过一个配置文件,你可以为你的网站生成一套专属的实用工具 CSS。

    ssh 注:这里原作者没有深入介绍,为什么说是一套命名约定呢而不是生成一些定死的 CSS 呢?

    举几个例子让大家感受一些,Tailwind 提供了一套强大的构建系统,比如默认情况下它提供了一些响应式的断点值:

    // tailwind.config.js
    module.exports = {
      theme: {
        screens: {
          'sm': '640px',
          // => @media (min-width: 640px) { ... }
    
          'md': '768px',
          // => @media (min-width: 768px) { ... }
    
          'lg': '1024px',
          // => @media (min-width: 1024px) { ... }
    
          'xl': '1280px',
          // => @media (min-width: 1280px) { ... }
        }
      }
    }

    你可以随时在配置文件中更改这些断点,比如你所需要的小屏幕 sm 可能指的是更小的 320px,那么你想要在小屏幕时候采用 flex 布局,还是照常写 sm:flex,遵循同样的约定,只是这个 sm 已经被你修改成适合于项目需求的值了。

    在比如说,Tailwind 里的 spacing 掌管了 margin、padding、width 等各个属性里的代表空间占用的值,默认是采用了 rem 单位,当你在配置里这样覆写后:

    // tailwind.config.js
    module.exports = {
      theme: {
        spacing: {
          '1': '8px',
          '2': '12px',
          '3': '16px',
          '4': '24px',
          '5': '32px',
          '6': '48px',
        }
      }
    }

    你再去写 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 有密切关系。这两种方法都提倡使用标签进行样式化。以某种方式试图模仿内联样式,这让它们有了很多相似的特性(比如在移动某些功能的时候更有信心)。

    Christopher Chedeau 一直致力于推广 React 生态系统中 CSS-in-JS 理念。在很多次演讲中,他都解释了 CSS 的问题:

    1. 全局命名空间
    2. 依赖
    3. 无用代码消除
    4. 代码压缩
    5. 共享常量
    6. 非确定性(Non-Deterministic)解析
    7. 隔离

    实用工具/原子 CSS 也解决了其中的一些问题,但也确实没法解决所有问题(特别是样式的非确定性解析)。

    如果它们有很多相似之处,那我们能否同时使用它们呢?

    探索原子 CSS-in-JS

    原子 CSS-in-JS 可以被视为是“自动化的原子 CSS”:

  • 你不再需要创建一个 class 类名约定
  • 通用样式和一次性样式的处理方式是一样的
  • 能够提取页面所需要的的关键 CSS,并进行代码拆分
  • 有机会修复 JS 中 CSS 规则插入顺序的问题
  • 我想强调两个特定的解决方案,它们最近推动了两个大规模的原子 CSS-in-JS 的部署使用,来源于以下两个演讲。

    React-Native-Web at Twitter (更多细节在Nicolas Gallagher 的演讲)。

    Stylex at Facebook (更多细节在Frank Yan 的演讲)。

    也可以看看这些库:

  • 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 桌面应用。

    Stylex

    Stylex 是一个新的 CSS-in-JS 库,Facebook 团队为了 2020 年的 Facebook 应用重构而开发它。未来可能会开源,有可能用另一个名字。

    值得一提的是,React-Native-Web 的作者 Nicolas Gallagher 被 Facebook 招安。所以里面出现一些熟悉的概念一点也不奇怪。

    我的所有信息都来自演讲 :),还需要等待更多的细节。

    可扩展性

    不出所料,在 Atomic CSS 的加成下,Twitter 和 Facebook 的 CSS体积都大幅减少了,现在它的增长遵循的是对数曲线。不过,简单的应用则会多了一些 初始体积

    Facebook 分享了具体数字:

  • 旧的网站仅仅首页就用了 413Kb 的 CSS
  • 新的网站整个站点只用了 74Kb,包括暗黑模式
  • 源码和输出

    这两个库的 API 看起来很相似,但也很难说,因为我们对 Stylex 了解不多。

    值得强调的是,React-Native-Web 会扩展 CSS 语法糖,比如 margin: 0,会被输出为 4 个方向的 margin 原子规则。

    以一个组件为例,来看看旧版传统 CSS 和新版原子 CSS 输出的区别。

    <Component1 classNames="class1" /> <Component2 classNames="class2" />
    .class1 {
      background-color: mediumseagreen;
      cursor: default;
      margin-left: 0px;
    }
    .class2 {
      background-color: thistle;
      cursor: default;
      jusify-content: flex-start;
      margin-left: 0px;
    }

    可以看出这两个样式中 cursormargin-left 是一模一样的,但它在输出中都会占体积。

    再来看看原子 CSS 的输出:

    <Component1 classNames="classA classC classD" />
    <Component2 classNames="classA classB classD classE" />
    class a {
      cursor: default;
    }
    class b {
      background-color: mediumseagreen;
    }
    class C {
      background-color: thistle;
    }
    class D {
      margin-left: 0px;
    }
    class E {
      jusify-content: flex-start;
    }

    可以看出,虽然标签上的类名变多了,但是 CSS 的输出体积会随着功能的增多而减缓增长,因为出现过一次的 CSS Rule 就不会再重复出现了。

    生产环境验证

    我们看看 Twitter 上的标签是什么样子的:

    现在,让我们来看看新 Facebook:

    很多人可能会被吓到,但是其实它很好用,而且保持了 可访问性。

    在 Chrome 里检查样式可能有点难,但 devtools 里就看得很清楚了:

    CSS 规则顺序

    与手写的工具/原子 CSS 不同,JS 库能让样式不依赖于 CSS 规则的插入顺序。

    在规则冲突的情况下,生效的不是标签上 class attribute 中的最后一个类,而是样式表中最后插入的规则。

    以这张图为例,我们期望的是书写在后blue 类覆盖前面的类,但实际上 CSS 会以样式表中的顺序来决定优先级,最后我们看到的是红色的文字。

    在实际场景中,这些库避免在同一个元素上写入多个规则冲突的类。它们会确保标签上书写在最后的类名生效。其他的被覆盖的类名则被规律掉,甚至压根不会出现在 DOM 上。

    const styles = pseudoLib.create({
      red: {color: "red"},
      blue: {color: "blue"},
    });
    
    // 只会输出 blue 相关的 CSS
    <div style={[styles.red, styles.blue]}>
      Always blue!
    </div>
    
    // 只会输出 red 相关的 CSS
    <div style={[styles.blue, styles.red]}>
      Always red!
    </div>

    注意:只有使用最严格的原子 CSS 库才能实现这种可预测的行为。

    如果一个类里有多个 CSS 规则,并且只有其中的一个 CSS 规则被覆盖,那么 CSS-in-JS 库没办法进行相关的过滤,这也是原子 CSS 的优势之一。

    如果一个类只有一个简单的 CSS 规则,如 margin: 0,而覆盖的是 marginTop: 10。像 margin: 0 这样的简写语法被扩展为 4 个不同的原子类,这个库就能更加轻松的过滤掉不该出现在 DOM 上的类名。

    仍然喜欢 Tailwind?

    只要你熟悉所有的 Tailwind 命名约定,你就可以很高效的完成 UI 编写。一旦你熟悉了这个设定,就很难回到手写每个 CSS 规则的时代了,就像你写 CSS-in-JS 那样。

    没什么能阻止你在原子 CSS-in-JS 的框架上建立你自己的抽象 CSS 规则,Styled-system 就能在 CSS-in-JS 库里完成一些类似的事情。它基于一些约定创造出一些原子规则,在 emotion 中使用它试试:

    import styled from '@emotion/styled';
    import { typography, space, color } from 'styled-system';
    
    const Box = styled('div')(typography, space, color);

    等效于:

    <Box
      fontSize={4}
      fontWeight="bold"
      p={3}
      mb={[4, 5]}
      color="white"
      bg="primary"
    >
      Hello
    </Box>

    甚至有可能在 JS 里复用一些 Tailwind 的命名约定,如果你喜欢的话。

    先看些 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]} />;

    就生产力的角度而言,并没有太大的不同。甚至可以用 TS 来避免错别字。

    结论

    这就是我要说的关于原子 CSS-in-JS 所有内容。

    我从来没有在任何大型生产部署中使用过原子 CSS、原子 CSS-in-JS 或 Tailwind。我可能在某些方面是错的,请随时纠正我。

    我觉得在 React 生态系统中,原子 CSS-in-JS 是一个非常值得关注的趋势,我希望你能从这篇文章中学到一些有用的东西。

    感谢阅读。

    相关推荐

    如何设计一个优秀的电子商务产品详情页

    加入人人都是产品经理【起点学院】产品经理实战训练营,BAT产品总监手把手带你学产品电子商务网站的产品详情页面无疑是设计师和开发人员关注的最重要的网页之一。产品详情页面是客户作出“加入购物车”决定的页面...

    怎么在JS中使用Ajax进行异步请求?

    大家好,今天我来分享一项JavaScript的实战技巧,即如何在JS中使用Ajax进行异步请求,让你的网页速度瞬间提升。Ajax是一种在不刷新整个网页的情况下与服务器进行数据交互的技术,可以实现异步加...

    中小企业如何组建,管理团队_中小企业应当如何开展组织结构设计变革

    前言写了太多关于产品的东西觉得应该换换口味.从码农到架构师,从前端到平面再到UI、UE,最后走向了产品这条不归路,其实以前一直再给你们讲.产品经理跟项目经理区别没有特别大,两个岗位之间有很...

    前端监控 SDK 开发分享_前端监控系统 开源

    一、前言随着前端的发展和被重视,慢慢的行业内对于前端监控系统的重视程度也在增加。这里不对为什么需要监控再做解释。那我们先直接说说需求。对于中小型公司来说,可以直接使用三方的监控,比如自己搭建一套免费的...

    Ajax 会被 fetch 取代吗?Axios 怎么办?

    大家好,很高兴又见面了,我是"高级前端进阶",由我带着大家一起关注前端前沿、深入前端底层技术,大家一起进步,也欢迎大家关注、点赞、收藏、转发!今天给大家带来的主题是ajax、fetch...

    前端面试题《AJAX》_前端面试ajax考点汇总

    1.什么是ajax?ajax作用是什么?AJAX=异步JavaScript和XML。AJAX是一种用于创建快速动态网页的技术。通过在后台与服务器进行少量数据交换,AJAX可以使网页实...

    Ajax 详细介绍_ajax

    1、ajax是什么?asynchronousjavascriptandxml:异步的javascript和xml。ajax是用来改善用户体验的一种技术,其本质是利用浏览器内置的一个特殊的...

    6款可替代dreamweaver的工具_替代powerdesigner的工具

    dreamweaver对一个web前端工作者来说,再熟悉不过了,像我07年接触web前端开发就是用的dreamweaver,一直用到现在,身边的朋友有跟我推荐过各种更好用的可替代dreamweaver...

    我敢保证,全网没有再比这更详细的Java知识点总结了,送你啊

    接下来你看到的将是全网最详细的Java知识点总结,全文分为三大部分:Java基础、Java框架、Java+云数据小编将为大家仔细讲解每大部分里面的详细知识点,别眨眼,从小白到大佬、零基础到精通,你绝...

    福斯《死侍》发布新剧照 &quot;小贱贱&quot;韦德被改造前造型曝光

    时光网讯福斯出品的科幻片《死侍》今天发布新剧照,其中一张是较为罕见的死侍在被改造之前的剧照,其余两张剧照都是死侍在执行任务中的状态。据外媒推测,片方此时发布剧照,预计是为了给不久之后影片发布首款正式预...

    2021年超详细的java学习路线总结—纯干货分享

    本文整理了java开发的学习路线和相关的学习资源,非常适合零基础入门java的同学,希望大家在学习的时候,能够节省时间。纯干货,良心推荐!第一阶段:Java基础重点知识点:数据类型、核心语法、面向对象...

    不用海淘,真黑五来到你身边:亚马逊15件热卖爆款推荐!

    Fujifilm富士instaxMini8小黄人拍立得相机(黄色/蓝色)扫二维码进入购物页面黑五是入手一个轻巧可爱的拍立得相机的好时机,此款是mini8的小黄人特别版,除了颜色涂装成小黄人...

    2025 年 Python 爬虫四大前沿技术:从异步到 AI

    作为互联网大厂的后端Python爬虫开发,你是否也曾遇到过这些痛点:面对海量目标URL,单线程爬虫爬取一周还没完成任务;动态渲染的SPA页面,requests库返回的全是空白代码;好不容易...

    最贱超级英雄《死侍》来了!_死侍超燃

    死侍Deadpool(2016)导演:蒂姆·米勒编剧:略特·里斯/保罗·沃尼克主演:瑞恩·雷诺兹/莫蕾娜·巴卡林/吉娜·卡拉诺/艾德·斯克林/T·J·米勒类型:动作/...

    停止javascript的ajax请求,取消axios请求,取消reactfetch请求

    一、Ajax原生里可以通过XMLHttpRequest对象上的abort方法来中断ajax。注意abort方法不能阻止向服务器发送请求,只能停止当前ajax请求。停止javascript的ajax请求...