CSS-in-JS 指南
开发人员在使用 JavaScript 框架时经常面临的一个难题是是否使用 CSS-in-JS。如果你是 React 开发人员,你很可能之前使用过 CSS-in-JS。
如今,CSS 与 CSS-in-JS 是一个热门话题。这主要是因为 CSS-in-JS 的性能问题引起了人们的关注。然而,在不久的将来,也有一些新的 CSS 特性可以解决这些问题。
本文的目的是根据现代 CSS 的当前状态以及它在未来可能发生的变化,帮助你在即将进行的项目中选择 CSS 和 CSS-in-JS。
请注意,任何 JavaScript 前端框架或库都可以实现 CSS-in-JS 的思想。本文使用 React——迄今为止最流行的 JavaScript 前端库——来讨论 CSS-in-JS 的应用,以及它显著的优点和缺点。
1. CSS 的渲染阻塞
在进一步讨论什么是最好的,什么是不好的之前,让我们先讨论一下 CSS 引起的渲染问题。
传统上,浏览器首先加载 HTML,然后从所有外部资源加载 CSS。之后,浏览器使用所有外部和内部 CSS 信息创建 CSSOM。现在,浏览器已经准备好根据 CSS 级联规则为呈现的 HTML 提供样式了。
这个过程会导致 CSS 阻塞页面的呈现,并延迟所请求页面的第一次绘制。首次绘制是浏览器为所请求的页面在屏幕上打印第一个像素时发生的事件。
在第一次绘制时超过半秒的延迟会带来更大的用户不满风险,并可能对应用程序的目标产生负面影响。向客户端交付 CSS 的速度越快,就越能优化页面首次绘制的时间。
2. 解决 CSS 的渲染阻塞
使用 HTTP/2 支持的应用程序,多个 HTML、CSS 和 JS 文件可以并行加载。这种能力在 HTTP/1.1 中受到限制。大多数现代浏览器和网站现在都支持 HTTP/2,这可以最大限度地减少由于等待其他文件加载而导致的渲染阻塞:
然而,渲染阻塞还涉及到文件加载速度以外的其他因素。
假设我们的应用程序的一个页面有很多 CSS。它可能包含不使用的选择器,但因为我们在每个页面上导入一个主 CSS 文件而存在。
上面的场景基本上描述了我们如何习惯直接使用 CSS UI 框架或我们创建的 UI 工具包来快速简化我们的设计系统。并非每个页面都使用该框架或工具包引用的所有样式。结果,我们在页面的最终 CSS 样式中得到了很多的垃圾。
CSS越多,浏览器构建 CSSOM 所需的时间就越长,这将导致完全不必要的渲染阻塞。
为了解决这个问题,将 CSS 分成小块是非常有用的。换句话说,将全局样式和关键 CSS 保存在一个通用 CSS 文件中,然后将其他所有内容组件化。这个策略更有意义,解决了不必要的阻塞问题:
上图显示了在 React 中为不同组件创建和管理独立 CSS 文件的传统方法。因为每个 CSS 文件都直接附加到它各自的组件上,所以它只在相关组件被导入时才会导入,当该组件被移除时就会消失。
这个方法有一个缺点。假设我们的应用程序包含 100 个组件,而从事同一项目的其他开发人员意外地在其中一些 CSS 文件中使用了相同的类名。
在这里,每个组件的每个 CSS 文件的作用域都是全局的,所以这些意外复制的样式会继续覆盖彼此,并被全局应用。这样的场景将导致严重的布局和设计不一致。
CSS-in-JS 据说可以解决这个范围问题。接下来的部分将从高层次上回顾 CSS-in-JS,并讨论它是否能一次性有效地解决范围问题。
3. CSS-in-JS 提供了什么
CSS-in-JS,简而言之,是一个外部功能层,允许你通过 JavaScript 为组件编写 CSS 属性。
这一切都始于 2015 年一个名为 JSS 的 JavaScript 库,该库仍在积极维护中。你必须使用 JavaScript 语法向选择器提供 CSS 属性,一旦页面加载,选择器就会自动将这些属性应用于各自的选择器。
当 JavaScript 用 React 等库接管了渲染和管理前端时,一种称为样式组件的 CSS-in-JS 解决方案出现了。下面我们将用 styled-components
库演示 CSS-in-JS 的一个示例用例,因为它是 React 中使用 CSS-in-JS 的最流行的方式。
在 React 应用中,使用下面的 Yarn 命令安装 styled-components
库。如果你正在使用不同的包管理器,请参阅样式化组件安装文档以找到适当的安装命令:
yarn add styled-components
在安装了 styled-components
库之后,导入 styled
函数,并按照下面的代码使用它:
import styled from "styled-components"; const StyledButton = styled.a` padding: 0.75em 1em; background-color: ${ ({ primary }) => ( primary ? "#07c" : "#333" ) }; color: white; &:hover { background-color: #111; } `; export default StyledButton;
这个有样式的组件现在可以被导入到任何地方,并直接用于构建功能组件,而不必担心样式:
import StyledButton from './components/styles/Button.styled'; function App() { return ( <div className="App"> ... <StyledButton href="...">Default Button</StyledButton> <StyledButton primary href="...">Primary Button</StyledButton> </div> ); } export default App;
效果如下:
请注意,应用于有样式组件的样式是局部作用域的,这消除了需要注意 CSS 类命名和全局作用域的麻烦。此外,我们可以根据提供给组件的 props
或应用程序功能所需的任何其他逻辑动态添加或删除 CSS。
4. CSS-in-JS 的优点
JavaScript 开发人员可能更喜欢使用 CSS-in-JS 样式,而不是使用 CSS 类。CSS-in-JS 方法解决的最大问题是全局作用域。对于 JavaScript 开发人员来说,它还有一些其他的优势。
现在让我们来探讨一下这些优点。
4.1 没有范围和优先级问题
由于样式在局部范围内可用,因此它们不容易与其他组件的样式冲突。你甚至不必担心严格地命名事物以避免样式冲突。
样式是专门为一个组件编写的,没有预先设置子选择器,因此很少出现优先级问题。
4.2 动态样式
条件 CSS 是 CSS-in-JS 的另一个亮点。正如上面的按钮示例所演示的,检查 prop
值并添加合适的样式。
4.3 简单的主题
用自定义 CSS 属性为应用设置主题是很方便的。最后,你将不得不转移到 JavaScript 方面,并编写逻辑来切换和记住基于用户输入的主题。
CSS-in-JS 允许你完全用 JavaScript 编写主题逻辑。使用 styled-components
ThemeProvider
包装器,可以快速对组件的主题进行颜色编码。
4.4 易于维护
考虑到 CSS-in-JS 提供的特性和优势,JavaScript 开发人员可能会发现 CSS-in-JS 比管理数百个 CSS 文件更方便。然而,一个人必须对 JavaScript 和 CSS 都有很好的理解,才能有效地管理和维护由 CSS-in-JS 支持的大型项目。
5. CSS-in-JS 的缺点
CSS-in-JS 确实很好地解决了范围问题。但正如我们最初讨论的那样,我们面临着更大的挑战——比如渲染阻塞,这会直接影响用户体验。除此之外,CSS-in-JS 的概念还需要解决其他一些问题。
5.1 延迟渲染
CSS-in-JS 将执行 JavaScript 从 JavaScript 组件解析 CSS,然后将这些解析后的样式注入到 DOM 中。组件越多,浏览器第一次绘制所花费的时间就越多。
5.2 缓存的问题
CSS 缓存通常用于提高连续页面加载时间。由于使用 CSS-in-JS 时不涉及 CSS 文件,缓存是一个大问题。此外,动态生成的 CSS 类名使这个问题更加复杂。
5.3 不支持 CSS 预处理器
使用常规的组件化 CSS 方法,很容易添加对 Sass、Less、PostCSS 等预处理器的支持。这在 CSS-in-JS 中是不可能的。
5.4 混乱的 DOM
CSS-in-JS 基于这样的思想:将所有的样式定义从 JavaScript 解析为普通的 CSS,然后使用样式块将这些样式注入到 DOM 中。
对于每个用 CSS-in-JS 样式化的组件,可能有 100 个样式块必须先解析,然后再注入。简单地说,将会有更多的间接成本。
5.5 学习曲线
CSS-in-JS 中缺少很多原生 CSS 和 SCSS 特性。对于习惯于 CSS 和 SCSS 的开发人员来说,适应 CSS-in-JS 可能需要花一定的时间。
5.6 没有广泛的支持
现在大多数 UI 和组件库都不支持 CSS-in-JS 方法,因为它仍然有很多问题需要解决。上面讨论的问题可能共同导致低性能、难以维护的产品,并且存在多个 UI 和 UX 不一致。