likes
comments
collection
share

React RSC 新范式的很好,除了不兼容 CSS-in-JS

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

大家好,这里是大家的林语冰。欢迎持续关注“前端俱乐部”。

去年今日,Next 13.4 发布,正式成为第一个基于 RSC(React 服务器组件)的 React 元框架。

如果不打碎几颗蛋蛋,就无法料理煎蛋卷。

RSC 提供了一种 React 编写服务器专属代码的官方方案,是一场 React 的认知革命,所以我们长期使用的某些第三方库已经乱成一麻。

最头大的是,诸如 styled-components 之类人气最高的 CSS-in-JS 库无法完美兼容 RSC。

不管你有没有使用 CSS-in-JS,本文仍有助于您深度学习 RSC,因为其中许多问题并不是 CSS-in-JS 的特定问题!

React RSC 新范式的很好,除了不兼容 CSS-in-JS

免责声明

本文属于是语冰的直男翻译了属于是,略有删改,仅供粉丝参考。英文原味版请传送 CSS in React Server Components

CSS-in-JS 的工作原理

首先我们谈谈 styled-components 等CSS-in-JS 库的工作原理。

举个栗子,我们可以在 React 组件中这样写 CSS:

import styled from 'styled-components'

const BigRedButton = styled.button`
  font-size: 2rem;
  color: red;
`

可以看到,我们没有将 CSS 写到诸如 .red-btn 之类的 CSS 类中,而是直接将样式应用到 React 组件。

这就是样式组件的特别之处:组件才是可复用的原语,而不是 CSS 类。

styled.button 是一个动态生成新 React 组件的函数,我们将该组件赋值给一个名为 BigRedButton 的变量。

然后我们可以像使用其他 React 组件一样使用它,这会渲染一个带有大红色文本的 <button> 标记。

React RSC 新范式的很好,除了不兼容 CSS-in-JS

但 CSS-in-JS 库到底如何将 CSS 样式应用于组件呢?

在“前 CSS-in-JS 时代”,我们主要有三大方案:

  1. 样式通过 style 属性内联应用。
  2. 样式单独写到 CSS 文件中,并通过 <link> 标记加载。
  3. 样式写到 <style> 标记中,通常放在当前 HTML 文档的 <head> 标记中。

如果我们运行上述代码并检查 DOM,CSS-in-JS 的原理就会揭晓:

<html>
  <head>
    <style data-styled="active">
      .abc123 {
        font-size: 2rem;
        color: red;
      }
    </style>
  </head>
  <body>
    <button className="qaq996">Click me!</button>
  </body>
</html>

styled-components 会将我们的样式写入 CSS-in-JS 库管理的 <style> 标记中。

为了将这些样式应用到这个特定的 <button> 组件,它会生成一个唯一的类名 ".qaq996"

同理可得,当用户与我们的应用交互时,可能需要修改或销毁某些样式。

举个栗子,假设我们有一个条件渲染的样式组件:

function Header() {
  const user = useUser()
  return (
    <>
      {user && <SignOutButton onClick={user.signOut}>Sign Out</SignOutButton>}
    </>
  )
}

const SignOutButton = styled.button`
  color: white;
  background: red;
`

起初,如果 user 值为 undefined,那么 <SignOutButton> 不会被渲染,因此这些样式都不存在。

稍后,如果用户登录,应用会重新渲染,且样式组件会启动,将这些样式注入到 <style> 标记中。

本质上,每个样式组件都是一个常规的 React 组件,但它们有一个额外的副作用:即将样式渲染到 <style> 标记。

不兼容的根本原因

RSC 与 CSS-in-JS 的不兼容性在于,样式组件被设计为在浏览器中运行,而 RSC 从不接触浏览器。

具体而言,styled-components 大量使用 useContext 钩子,这与 React 生命周期绑定,但 RSC 没有 React 生命周期。

因此,如果我们想在 RSC 新范式中使用样式组件,那么每个渲染单独样式组件的 React 组件都需要变成客户端组件。

现实开发中,可能 90% 的组件文件会使用样式组件。这意味着,这种不兼容性使我们无法充分利用 RSC 新范式的全部威力。

如果让我改变 RSC 的一件事,那应该是“客户端组件”的命名。这个名称容易误解为,这些组件仅在客户端渲染。

但事实并非如此!粉丝请记住,所谓“客户端组件”只是新瓶装旧酒。

事实上,2023 年 5 月之前创建的 React 应用中的每个组件其实就是客户端组件。

库维护者在搞什么飞机?

您可能想知道,为什么 styled-components 的维护者没有更新他们的库,兼容 RSC?我们知道这个问题已经一年有余,为什么他们还无动于衷?

事实上,目前 styled-components 维护者因为 React 缺少 API 而束手无策。

具体而言,React 尚未提供 RSC 友好的 Context 替代方案,而样式组件需要某种在组件之间共享数据的方案,以便在服务器端渲染期间正确应用所有样式。

据我所知,唯一的解决方案是完全重写整个库,使用完全不同的方法。

但这不仅会导致重大的破坏性更新,而且期望由社区志愿者组成的开源维护团队完成这件事也难上加难。

零运行时的 CSS-in-JS 库

幸运的是,React 社区并没有因此一蹶不振!目前正在开发的几个库,它们提供类似样式组件的 API,但完全兼容 RSC!

这些新工具没有与 React 生命周期耦合,而是采用了不同的方案:所有处理都在编译时完成

React RSC 新范式的很好,除了不兼容 CSS-in-JS

举个栗子,Linaria 早在 2017 年就被创建了,它几乎和样式组件一样古老!

其 API 看起来与样式组件大同小异:

import styled from '@linaria/react'

const BigRedButton = styled.button`
  font-size: 2rem;
  color: red;
`

真正画龙点睛的一点在于:在编译步骤中,Linaria 会转换上述代码,并将所有样式移至 CSS 模块中。

运行 Linaria 后,代码将如下所示:

/* /components/Home.module.css */
.BigRedButton {
  font-size: 2rem;
  color: red;
}
/* /components/Home.js */
import styles from './Home.module.css'

export default function Homepage() {
  return <button className={styles.BigRedButton}>Click me!</button>
}

如果您还不熟悉 CSS 模块,它是 CSS 的轻量级抽象。我们可以将其视为纯 CSS,但不必担心全局命名的唯一性。

在编译步骤中,Linaria 发挥其魔力后,诸如 .BigRedButton 之类的通用名称就会转换为诸如 .qaq996 之类的独特名称。

最重要的是,CSS 模块已经得到广泛支持,这是人气最高的选择之一。诸如 Next 之类的元框架已经对 CSS 模块提供了一流支持。

我们可以编写样式组件,Linaria 会将它们预处理为 CSS 模块,然后再将其处理为纯 CSS。所有这些都发生在编译时。

那么,我们应该立刻马上将样式组件应用迁移到 Linaria 吗?

不幸的是,虽然 Linaria 本身在积极维护,但它和 Next 没有官方捆绑,并且让 Linaria 与 Next “梦幻联动”并非易事。

人气最高的 next-linaria 集成已经 3 年没有更新了,且不兼容 App Router 和 RSC。

next-with-linaria 是一个新的选择,但它有一个大大的警告:禁止在生产中使用!

Pigment CSS

Material UI 是人气最高的 React 组件库之一,它构建在 CSS-in-JS 库 emotion 之上。

Material 团队一直在努力解决有关 RSC 兼容性的问题,他们最近开源了一个全新的工具库,名为 Pigment CSS(颜料),其 API 看起来似曾相识:

import { styled } from '@pigment-css/react'

const BigRedButton = styled.button`
  font-size: 2rem;
  color: red;
`

Pigment CSS 在编译时运行,它使用与 Linaria 相同的策略,编译为 CSS 模块。Next 和 Vite 都有对应插件

事实上,Pigment CSS 使用了一个名为 WyW-in-JS(“What you Want in JS”)的低阶工具。该工具从 Linaria 代码库发展而来,隔离了“编译为 CSS 模块”业务逻辑,诸如 Pigment CSS 之类的库可以在其之上构建自己的 API。

CSS 模块已经历经了大量考验和优化。就目前而言,Pigment CSS 十分出色,具有优秀的性能和 DX,是完美的解决方案。

Material UI 在 NPM 上每周下载约 500 万次,约为 React 本身的 1/5!而 Material UI 的下一个主版本会支持 Pigment CSS,并计划最终完全弃坑 emotion

因此,Pigment CSS 未来可能会成为人气最高的 CSS-in-JS 库之一。

前方的路

如果您有一个使用“旧版”CSS-in-JS 库的生产应用,您该怎么办?

大多数情况下,我认为你不需要慌不择路,虽然这可能有点违反直觉。

React 社区的众口嚣嚣听起来好像你绝对不能在现代 React 或 Next 应用中使用样式组件,甚至会带来巨大的性能损失。

但事实并非如此。这是因为,许多道友将 RSC 和 SSR 混为一谈。

SSR 的工作方式仍然与以往完全相同,它不受这些东东的影响。如果您迁移到 Next 的 App Router 或其他 RSC 实现,您的应用也不会变慢。事实上,它可能会变得更快!

从性能角度来看,RSC 和零运行时 CSS 库的主要好处是 TTI(交互时间)。这是向用户展示 UI 和完全交互 UI 之间的延迟。

如果忽视 TTI,可能会产生糟糕的用户体验。用户会点击按钮,但可能无事发生,因为应用程序仍处于水合过程中。

因此,应用迁移到零运行时 CSS 库会对用户体验产生积极影响。

本期话题是 —— 你在 React 和 RSC 中分别使用哪些样式方案呢,以及有哪些优缺点?欢迎在本文下方自由言论,文明共享。

欢迎持续关注“前端俱乐部”。坚持阅读,自律打卡,每天一次,进步一点。

谢谢大家的点赞,掰掰~

React RSC 新范式的很好,除了不兼容 CSS-in-JS