likes
comments
collection
share

CSS-in-JS 到底是什么?

作者站长头像
站长
· 阅读数 73

原文链接:What actually is CSS-in-JS?,2019.01.29,Oleg Isonen

导读:本文是偏理论性质的。介绍 CSS-in-JS 理念产生的背景,CSS 这门语言本身会带来的问题,以及相比传统 CSS 写法,CSS-in-JS 具备哪些优势。当然,作者并没有吹嘘 CSS-in-JS 是终极解决方案,但至少在目前阶段,可以起到一个引路人的角色。

CSS-in-JS 是指一系列解决 CSS 复杂问题的想法。由于它不是特定的库,因此不同的库可能会使用不同的方法来解决不同的子集问题,这取决于这些库的实现细节。

然而,所有实现都共同使用 API 来处理问题,而非传统方式,并利用 JavaScript 作为样式编写语言。

如果你有兴趣了解 CSS-in-JS 方法的权衡,请参阅我撰写的另一篇文章

缺少模块

CSS 历史上从未真正拥有模块,JavaScript 也是如此。随着 Web 应用程序的不断发展,JavaScript 添加了一个模块系统。最开始它是作为一种附加解决方案(CommonJS)的形式,后来发展成为标准、可静态分析的模块系统,即我们熟知的 ECMAScript Modules(ESM)。

模块背后的原理适用于 JavaScript 和 CSS:只公开 API、隐藏实现细节。我们需要能够显式地解耦应用程序子系统,以便以更可预测地方式修改代码。

并非每个应用程序都需要这样做,但模块使得维护中大型应用程序变得更容易,并简化了内部实现细节的修改和删除。问题会随着项目复杂性的增加,慢慢出现;而小型应用程序一般复杂性没那么大,所以问题也不会很明显。

CSS-in-JS 依赖于 JavaScript 的模块实现。

缺少作用域

我们知道 CSS 一直有一个单一的全局命名空间,例如,类可以添加到任何元素中,标签选择器可以选中文档中的任何元素。最初创建 CSS 是为了样式化文档,并不需要组件。整个页面被视为一个大块(one big chunk)进行样式设置,并且通常没有涉及许多人共同工作。不过慢慢的,许多网站的复杂性已经显著增加,这也是许多 CSS 方法论被创建的主要原因之一。当多年来许多人参与项目时,很难建立和始终执行任何一种规则。

现代网站复杂度一般都比较高,需要许多前端专家在站点的不同区域分别工作。这些部分以不同方式在全局站点中重复使用,并要求这些区域块是完全可交互和功能齐备的。

没有统一作用域的限制会导致样式泄漏并具有不可预测性后果。

这是一个简化的示例,展示了CSS-in-JS库如何生成选择器:

function css = styleBlock => {
  const className = someHash(styleBlock);
  const styleEl = document.createElement('style');
  styleEl.textContext = `
    .${className} {
    	${styleBlock}
    }
  `;
  return className;
}

const className = css(`
	color: red;
  padding: 20px;
`); // 'c23j4'

CSS-in-JS 通过生成唯一选择器来自动化解决作用域问题。

隐式依赖

CSS 提供了规则级别(rule level)的代码重用,这表示可以重用样式块,一个规则有一个选择器。当选择器应用于元素时,会应用整个样式块。这可以通过以下两种方式实现:

  1. CSS 规则包括多个选择器以便设置不同的 HTML 元素。
  2. HTML 元素可以同时使用多个类名,并且属性也能被 CSS 选中并设置规则。

但是这两种方法都不太好,因为它们都会导致庞大的代码结构,其中互相依赖特别多,很难清晰地隔离子系统。

在第一种情况下,当我们将许多选择器添加到单个 CSS 规则中时,该规则包含对其他子系统的引用。修改任意一个子系统都要不的不考虑到这条规则的影响,并且很容易忘记。

在第二种情况下,使用多个类名或其他属性会导致元素被多个 CSS 规则制约。这使得依赖关系再次变得复杂,并且很容易忘记哪些是需要删除的。

在这两种情况下,我们创建了难以理解和随时间变化而变化的依赖关系,在没有良好严格的体系支持下,人们很难保持一致的写法。

CSS-in-JS 使依赖关系变得明确,因为变量始终在代码中引用值。它们是可追踪的,因为我们可以静态分析值来自何处。它们是粒度化的,因为我们可以根据需要,重复使用 CSS 值、属性或整个样式块。

CSS-in-JS 推崇显式、可追踪和细粒度的依赖关系。

死代码

由于 HTML 和 CSS 之间的隐式关系,通常很难追踪未使用的 CSS 规则并通知作者或从包中删除它们。我们经常无法知道规则是不是在哪里被使用了。例如,可能有多个代码库或类名可能会根据生成 HTML 的语言进行条件应用,或者客户端 JavaScript 可能会操纵类名。

随着时间的推移,死代码可能会对网站性能和开发人员理解代码产生负面影响。

由于 JavaScript 中显式、可跟踪的变量和模块,我们可以就能创建 CSS 规则与 HTML 元素之间明确的连接,实现一种解决方案。

CSS-in-JS 有助于删除死代码。

不确定的样式加载顺序

如果您构建了单页应用程序(SPA)并为每个页面都拆分除了一个 CSS bundle 文件,则可能会出现不确定的样式加载顺序。在这种情况下,注入 CSS 的顺序取决于用户操作,导致选择器不可预测地应用于 HTML 元素。

想象一下,你加载了页面 A,然后在不刷新页面的情况下切换到了 B 页面。从技术上讲,你加载了 CSS-A,然后是 CSS-B。如果在 CSS-B 中使用的选择器能覆盖来自 CSS-A 的选择器,那还好,因为稍后加载的 B 的 CSS,具有更高的权重。

如果下一个用户直接从链接进入页面 B,然后切换到页面 A,则首先加载 CSS-B,然后是 CSS-A,在此过程中导致 CSS-A 具有更高权重,再且回到页面 B 样式可能就有问题了。现在不得不为所有可能存在的入口点之间的导航创建视觉回归测试了。

要解决这个问题,可以依赖紧密耦合的 CSS 和 HTML ,以便我们始终知道当前呈现的 HTML 使用到了哪些 CSS。

CSS-in-JS 有助于避免不确定的样式加载顺序。

一对多关系

将关注点基于语言进行分离的想法忽略了一个事实,即 CSS 并不是真正与 HTML 分离设计的。

CSS 对 HTML 结构有隐含的假设。例如,flexbox 布局假定要定位的容器是应用到它所在元素的直接子元素上。

当我们在应用程序中将 CSS 规则应用于不同的 HTML 元素时,可以基本上描述为“一对多关系”。如果更改 CSS 规则,则可能需要修改所有相关联的 HTML 元素。

CSS-in-JS 鼓励此关系变成一对一的,同时仍保持共享属性的能力。目前,并非每个 CSS-in-JS API 都强制执行一对一关系,因为许多库支持无相应 HTML 的 CSS 重用。我们需要让开发人员意识到这一点!

无论使用哪种抽象或根本不使用抽象,在重用 CSS 方面最好的方法是重用 HTML 并确保其所需的 CSS 自动呈现。

CSS-in-JS 鼓励将 CSS 和 HTML 耦合在一起。

万能选择器

有趣的是,一些人认为 CSS 太强大了,而另一些人则认为 CSS-in-JS 在抽象层面上太强大了。

事实上,它们都很强大,但是在不同的领域。CSS 选择器太强大了,因为它们可以针对文档中的任何元素进行定位。这是一个巨大的问题,因为我们试图编写只能访问 HTML 块或组件内部元素的 CSS。

CSS-in-JS 通过限制其选择器作用域来帮助约束这个功能。尽管仍不完美,但如果给定的库支持级联,则这些选择器仍然可以进入任何子元素。这比默认编写更受限制的 CSS 来看,是一个很好的飞跃。目前绝大多数 CSS-in-JS 库都支持级联,并非因为它安全可靠,而是因为实用且没有更好、更安全的方案出现。Shadow root CSS 还没有达到被广泛采用的水平。

另一方面,JavaScript 是一种更加强大的语言,不论在语法表现力上,还是模式和符号的选择上 。复杂 UX 逻辑通常难以在没有条件、函数和变量等情形下将内容时表达清楚。当运行时高度专门化用例时(highly specialized for the use case),纯声明式语法可以很好地工作,而 CSS 则用于完成各种任务。

CSS-in-JS 为开发者提供了更多的表现力,同时鼓励使用比级联更易于维护的模式。

基于状态的样式

CSS-in-JS 可以实现的一个非常强大的模式,就是基于状态的样式。从技术上讲,它通常被实现为一个 JavaScript 函数,该函数接收一个状态对象并返回 CSS 属性。因此,生成与元素状态相对应的 CSS 规则。与传统方式相比,与在其中构建包含多个类名的 class 属性,这具有一些优势:

  1. 负责最终 CSS 规则的逻辑可以访问状态,并且可以与其余样式放在一起。
  2. 生成 HTML 的逻辑通过避免类拼接逻辑变得更加简洁。

这两点都应该使复杂的、依赖于状态的 CSS 更加可读。

CSS-in-JS 为开发人员提供了API,不同于使用大量条件类名,可以更好描述基于状态的样式。

总结

我希望我能够让你了解 CSS-in-JS 概念背后的核心驱动因素。我的意图不是评判任何技术或提供完整的功能列表,因为这些在不同实现之间会有差异。同时,我也没有说 CSS-in-JS 是我们未来唯一可以使用的方案,但问题在于如果社区不能理解这些工具试图解决的问题,我们如何前进呢?

转载自:https://juejin.cn/post/7237053697527824440
评论
请登录