在 React Server Component 中使用 CSS - 理解 CSS-in-JS 和 React 的未来

原文信息: 查看原文查看原文

CSS in React Server Components

- Josh W. Comeau

介绍

去年的这个时候,Vercel 宣布了 Next 13.4 的稳定版本发布,成为首个构建在 React Server Component 之上的 React 框架。

这是一件大事!RSC(React Server Component)为我们提供了一种在 React 中编写 仅限服务器的代码 的官方方式。它打开了许多有趣的新可能。

但是不能丢掉一些蛋就做不出煎蛋了。RSC 是 React 工作方式的根本变化,我们一直在使用的一些库和工具已经变得混乱不堪。 😅

令人担忧的是,最受欢迎的 CSS-in-JS 库,如 💅 styled-components 和 Emotion,与 React 的这一新视觉并不完全兼容,而且目前尚没有明确的前进方向。

在过去的几个月里,我一直在研究这个问题,了解兼容性问题,并了解可行的解决方案。目前,我感觉对整个情况有了相当扎实的把握。我还发现了一些潜在的令人兴奋的进展。 ✨

如果你使用 CSS-in-JS 库,我希望这篇博文能帮助消除许多困惑,并为你提供一些实用的选择。

如果你 使用 CSS-in-JS 库,这篇博文也应该有助于加深你对 React Server Component 的理解。我们将在这里讨论的许多问题并不特定于 CSS-in-JS!

**只需使用 **

在线讨论时,其中最常见的建议之一是切换到不同的 CSS 工具。毕竟,在 React 生态系统中有数不尽的选择!

然而,对于我们许多人来说,这并不是一个切实可行的建议。我的博客和课程平台上有超过 5,000 个样式化组件。迁移到完全不同的工具比说起来要容易得多。

而且,即使我 能够 一挥手,换上完全不同的库,我也不想这么做。我真的很喜欢 styled API!

在本博文的后面,我们将讨论一些替代 CSS 库,但我们将重点关注与 styled-components 类似的 API 的选项。

解析 React Server Component

为了理解兼容性问题,我们需要了解 React Server Component。不过,在谈论这个之前,我们需要确保理解 服务器端渲染(SSR)。

SSR 是一个涵盖多种不同策略和实现的总称,但最典型的版本如下:

  1. 用户访问我们的 Web 应用程序。
  2. 请求被 Node.js 接收,它在无窗口的服务器环境中运行 React。它渲染我们的应用程序并生成一个包含所有初始 UI 的完整 HTML 文档。
  3. 当这个 HTML 文档在用户设备上加载时,React 将重新渲染所有相同的组件,重复服务器上执行的工作。然而,它不会生成 新的 HTML 元素,而是“采用”服务器生成的 HTML 元素。这被称为 水合

React 需要在用户设备上运行以处理交互。服务器生成的 HTML 是完全静态的;它不会包含我们编写的任何事件处理程序(例如 onClick),也不会捕获我们指定的任何引用(使用 ref 属性)。

好了,但为什么它必须重新做完全相同的工作?? 当 React 在用户设备上启动时,它会发现一堆预先存在的 UI,但它不会对此有任何 上下文,比如哪个组件拥有每个 HTML 元素。React 需要执行完全相同的工作来重建组件树,以便能够正确地连接现有的 HTML,将事件处理程序和引用附加到正确的元素上。React 需要绘制自己的地图,以便能够继续服务器离开的地方。

这个模型有一个重要的限制。 我们编写的所有代码都将在服务器上 客户端上执行。没有办法创建仅在服务器上渲染的组件。

假设我们正在构建一个具有数据库中数据的全栈 Web 应用程序。如果你是从像 PHP 这样的语言过来,你可能期望能够做类似这样的事情:

function Home() {
  const data = db.query('SELECT * FROM SNEAKERS');

  return (
    <main>
      {data.map(item => (
        <Sneaker key={item.id} item={item} />
      ))}
    </main>
  );
}

理论上,这段代码在服务器上可能完全正常工作,但是 完全相同的代码 将在用户设备上重新执行,这是个问题,因为客户端的 React 没有访问我们的数据库。没有办法告诉 React “只在服务器上运行此代码,并在客户端重用生成的数据”。

建立在 React 之上的元框架已经提出了自己的解决方案。例如,在 Next.js 中,我们可以这样做:

export async function getServerSideProps() {
  const data = await db.query('SELECT * FROM SNE

AKERS');

  return {
    props: {
      data,
    },
  };
}

function Home({ data }) {
  return (
    <main>
      {data.map(item => (
        <Sneaker key={item.id} item={item} />
      ))}
    </main>
  );
}

Next.js 团队说“好吧,所以完全相同的 React 代码必须在服务器和客户端上运行… 但是我们可以添加一些 额外 的代码,在 React 之外,仅在服务器上运行!”。

当 Next.js 服务器收到请求时,它将首先调用 getServerSideProps 函数,它返回的任何内容都将作为 props 提供给 React 代码。完全相同的 React 代码在服务器和客户端上运行,所以没有问题。相当聪明,对吧?

老实说,我今天仍然是这种方法的忠实拥护者。但它确实感觉有点像一种变通方法,一个由于 React 的限制而产生的 API。它也仅在 页面 级别、在每个路由的顶部才起作用;我们不能随意插入 getServerSideProps 函数。

React Server Component 为此问题提供了更直观的解决方案。 使用 RSC,我们可以在我们的 React 组件中直接执行数据库调用和其他仅限服务器的工作:

async function Home() {
  const data = await db.query('SELECT * FROM SNEAKERS');

  return (
    <main>
      {data.map(item => (
        <Sneaker key={item.id} item={item} />
      ))}
    </main>
  );
}

在“React Server Component”范式中,默认情况下,组件是 Server Component。Server Component 仅在服务器上运行。这段代码将 会在用户设备上重新运行;该代码甚至不会包含在 JavaScript 包中!

这种新的范式还包括 Client Component。Client Component 是在 服务器客户端 上运行的组件。你以前在“传统”(RSC 前)React 中编写的每个 React 组件都是 Client Component。这是一个旧东西的新名称。

我们通过文件顶部的新 "use client" 指令选择 Client Component:

'use client';

function Counter() {
  const [count, setCount] = React.useState(0);

  return (
    <button onClick={() => setCount(count + 1)}>
      Count: {count}
    </button>
  );
}

该指令创建了一个“客户端边界”;该文件中的所有组件,以及任何导入的组件,都将呈现为 Client Component,在服务器上首先运行,然后在客户端运行。

与其他 React 功能(例如钩子)不同,React Server Component 需要与捆绑器进行深度集成。截至我撰写本文的 2024 年 4 月,使用 React Server Component 的唯一实用方法是使用 Next.js,尽管我预计将来会发生变化。

Server Component 的局限性

理解 Server Component 的关键是它们不提供“完整”的 React 体验。大多数 React API 在 Server Component 中不起作用。

例如,useState。当状态变量更改时,组件会重新渲染,但 Server Component 无法 重新渲染;它们的代码甚至从未发送到浏览器,因此 React 将不知道如何处理状态更改。从 React 的角度来看,Server Component 生成的任何标记都是固定的,不能在客户端上更改。

同样,我们不能在 Server Component 内部使用 useEffect,因为效果不会在服务器上运行,它们只会在客户端渲染后运行。由于 Server Component 被排除在我们的 JavaScript 包之外,客户端的 React 将永远不会知道我们编写了 useEffect 钩子。

甚至 useContext 钩子也不能在 Server Component 内部使用,因为 React 团队尚未解决如何在 Server Component 和 Client Component 之间共享 React Context 的问题。

我是这样看待它的: Server Component 不是 真正的 React 组件,至少不是我们传统理解的那样。它们更像是 PHP 模板,由服务器渲染以创建原始 HTML。真正的创新在于 Server Component 和 Client Component 可以在同一个应用程序中共存!

更深入的探索

在这篇博文中,我想重点关注 React Server Component 的最重要细节,以便了解与 CSS-in-JS 框架的兼容性问题。

但如果你想更深入地了解 React Server Component,我还有一篇单独的博文,更深入地探讨了这个新世界:

CSS-in-JS 库的工作原理

好了,我们已经介绍了 React Server Component 的基础知识。现在让我们谈谈 CSS-in-JS 库如 💅 styled-components 的基础知识!

这里有一个快速示例:

import styled from 'styled-components';

export default function Homepage() {
  return (
    <BigRedButton>
      Click me!
    </BigRedButton>
  );
}

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

我们不是将 CSS 放在类似 .red-btn 的类中,而是将 CSS 附加到新生成的 React 组件。这就是 styled-components 特殊的地方;组件 是可重用的原始

,而不是类。

styled.button 是一个动态生成新 React 组件的函数,我们将该组件分配给一个名为 BigRedButton 的变量。然后我们可以像使用任何其他 React 组件一样使用该 React 组件。它将渲染一个具有大红文本的 <button> 标签。

但是这个库究竟是如何将此 CSS 应用于此元素的呢?我们有三个主要选项:

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

如果我们运行此代码并检查 DOM,答案就揭晓了:

<html>
  <head>
    <style data-styled="active">
      .abc123 {
        font-size: 2rem;
        color: red;
      }
    </style>
  </head>

  <body>
    <button className="abc123">
      Click me!
    </button>
  </body>
</html>

styled-components 将所提供的样式写入由库管理的 <style> 标签中。为了将这些样式与此特定的 <button> 相连接,它生成了一个唯一的类名,"abc123"

所有这些工作首先发生在初始 React 渲染期间:

  • 在客户端渲染环境中(例如 Parcel、create-react-app),<style> 标签是在用户设备上动态生成的,就像 React 创建的所有 DOM 节点一样。
  • 在服务器端渲染环境中(例如 Next、Remix),这项工作在服务器上进行。生成的 HTML 文档将包含此 <style> 标签。

随着用户与我们的应用程序进行交互,某些样式可能需要创建、修改或销毁。例如,假设我们有一个有条件渲染的 styled-components:

function Header() {
  const user = useUser();

  return (
    <>
      {user && (
        <SignOutButton onClick={user.signOut}>
          Sign Out
        </SignOutButton>
      )}
    </>
  );
}

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

最初,如果 user 未定义,<SignOutButton> 将不会被渲染,因此这些样式将不存在。稍后,如果用户登录,我们的应用程序将重新渲染,并且 styled-component 将启动,将这些样式注入到 <style> 标签中。

基本上,每个 styled 组件都是一个常规的 React 组件,但多了一个额外的小副作用:它们还将它们的样式渲染到 <style> 标签中。

对于我们今天的目的,这是最重要的收获,但如果你想深入了解库的内部工作原理,我写了一篇关于它的博文,名为 “解密 styled-components”

问题的关键

总结我们到目前为止学到的内容:

  • “React Server Component”是 React 的一个新范式,为我们提供了一种新类型的组件,即 Server Component。Server Component 仅在服务器上渲染。它们的代码甚至不包含在我们的 JS 包中。
  • styled-components 库允许我们动态创建附带 CSS 的 React 组件。它通过管理一个 <style> 标签来工作,该标签在组件重新渲染时会更新。

根本的不兼容性在于,styled-components 设计为在浏览器中运行,而 Server Component 从不接触浏览器。

在内部,styled-components 大量使用了 useContext 钩子。它旨在与 React 生命周期绑定,但 Server Component 没有 React 生命周期。因此,如果我们想在这个新的“React Server Component”世界中使用 styled-components,每个渲染 至少一个 styled-components 的 React 组件都需要成为 Client Component。

我不知道你怎么想,但对我来说,很少有 React 组件 包含任何样式。我估计我的组件文件中有超过 90% 使用 styled-components。这些组件中的大多数否则完全是静态的;它们不使用状态或任何其他 Client Component 功能。

这有点让人沮丧,因为这意味着我们无法充分利用这种新的范式…… 但事实上这并不是世界末日。

如果我能改变 React Server Component 的一件事,那就是“Client Component”这个名称。这个名称暗示这些组件 在客户端渲染,但事实并非如此。记住,“Client Component”是一个旧东西的新名称。每个 React 组件,在 2023 年 5 月之前创建的每个 React 应用程序中,都是 Client Component。

因此,即使在 styled-components 应用程序中,只有 10% 的组件可以成为 Server Component,这仍然是一个改进! 我们的应用程序仍然比 RSC 之前的世界轻量级和快速。我们仍然获得所有 SSR 的好处。这一点没有改变。

他们为什么不更新呢?

你可能想知道为什么 styled-components / Emotion 的维护者还没有更新他们的库以与 React Server Component 兼容。我们已经知道这个问题已经存在了一年多了,为什么他们还没有找到解决方案??

styled-components 的维护者目前受到 React 中缺失的 API 的阻碍。具体来说,React 还没有为 RSC 提供与 Context 兼容的替代方案,而 styled-components 需要 某种 方法在组件之间共享数据,以便在服务器端渲染期间正确应用所有样式。

几周前,我进行了 相当深入的探索,老实说,我很难想象在没有 React Context 的情况下如何使其正常工作。据我所知,唯一的解决方案是完全重写整个库以使用完全不同的方法。这不仅会导致重大的破坏性变化,而且也是对一个由志愿者开源维护者团队要求的完全不合理的事情。

如果你想了解更多信息,可以查看一个 styled-components 的 Github 问题,其中解释了阻碍的原因。我在 Emotion 存储库中也看到了类似的讨论。

无运行时 CSS-in-JS 库的世界

到目前为止,故事有点沉重。React Server Component 与 styled-components 之间 存在 根本性的不兼容性,而库维护者并没有获得他们需要的工具来添加支持。

幸运的是,React 社区并没有对这个问题置之不理! 有几个正在开发的库提供了类似于 styled-components 的 API,但与 React Server Component 完全兼容!✨

这些工具不是绑定到 React 生命周期中,而是采取了不同的方法;所有处理都在 编译时 进行。

现代 React 应用程序有一个构建步骤,在这一步中,我们将 TypeScript/JSX 转换为 JavaScript,并将成千上万个单独的文件打包成少数几个捆绑包。这项工作在我们的应用程序部署时完成,在它开始在生产环境中运行之前。为什么不在此步骤中处理我们的 styled 组件,而不是在运行时呢?

这是我们将在本节讨论的所有库的核心思想。让我们深入研究一下!

Linaria

Linaria 早在 2017 年 就创建了。它几乎和 styled-components 一样老!

API 看起来与 styled-components 完全相同:

import styled from '@linaria/react';

export default function Homepage() {
  return (
    <BigRedButton>
      Click me!
    </BigRedButton>
  );
}

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 的通用名称会被转换为像 .abc123 这样的唯一名称。

重要的是,CSS 模块已经得到了广泛的支持。 它是最受欢迎的选项之一。像 Next.js 这样的元框架已经对 CSS 模块提供了一流的支持。

因此,与其重新发明轮子并花费多年时间构建一个强大的生产就绪 CSS 解决方案,Linaria 团队决定走捷径。我们可以编写 styled-components,而 Linaria 将预处理它们为 CSS 模块,然后将其处理为纯 CSS。所有这些都发生在编译时。

运行时与编译时的权衡

在 RSC 出现之前很久,社区就已经在构建像 Linaria 这样的编译时库了。性能优势是令人信服的:styled-components 将 11 千字节(gzip)添加到我们的 JavaScript 捆绑包中,但 Linaria 添加的是 0kb,因为所有的工作都是提前完成的。此外,服务器端渲染会快一点,因为我们不需要花时间收集和应用样式。

尽管如此,styled-components 运行时并不是无用的。我们可以在 styled-components 中做一些在编译时无法做到的事情。例如,当某些 React 状态发生变化时,styled-components 可以动态更新 CSS。

幸运的是,自从 styled-components 第一次创建以来,CSS 已经变得更加强大。我们可以使用 CSS 变量来处理大多数动态用例。如今,运行时在某些情况下可能会提供稍微更好的开发人员体验,但在我看来,它实际上并不是必要的。

确实 意味着 Linaria 和其他编译时 CSS-in-JS 库不会真正成为 styled-components/Emotion 的即插即用替代品。我们将不得不花费一些时间重新设计动态组件。但与切换到完全不同的 CSS 工具相比,这只是工作的一小部分。

迁移到 Linaria

那么,我们应该将我们的 styled-components 应用程序都迁移到 Linaria 吗?

不幸的是,存在一个问题。虽然 Linaria 本身正在积极维护,但没有官方的 Next.js 绑定,而且让 Linaria 与 Next.js 一起工作并不容易。

最流行的集成方案,next-linaria,已经有 3 年没有更新了,并且不兼容 App Router / React Server Component。还有一个更新的选项,next-with-linaria,但是它有一个关于不在生产环境中使用的大警告。😅

因此,尽管这可能是一个适合冒险者的选择,但我并不认为这是我可以推荐的东

西。

Panda CSS

熊猫 CSS 吉祥物,一个可爱的熊猫滑板和珍珠奶茶

Panda CSS 是由构建了流行组件库 Chakra UI 的开发人员开发的现代 CSS-in-JS 库。

Panda CSS 有许多不同的接口。你可以像 Tailwind 一样使用它,指定简写类名,如 mb-5。你可以像 Stitches 一样使用它,使用变体和 cva。或者,你可以像 styled-components 一样使用它。

这是使用 styled API 的样子:

import { styled } from '../styled-system/jsx'

export default function Homepage() {
  return (
    <BigRedButton>
      Click me!
    </BigRedButton>
  );
}

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

与 Linaria 类似,Panda CSS 也会编译成 Tailwind 风格的实用类. 最终结果将类似于以下内容:

/* /styles.css */
.font-size_2rem {
  font-size: 2rem;
}
.color_red {
  color: red;
}
/* /components/Home.js */
export default function Homepage() {
  return (
    <button className="font-size_2rem color_red">
      Click me!
    </button>
  );
}

对于像 color: red 这样的每个唯一的 CSS 声明,Panda CSS 将在一个中央 CSS 文件中创建一个新的实用类。然后该文件将在我们 React 应用程序的每个路由上加载。

我真的很想喜欢 Panda CSS。它由一支经验丰富的团队开发,提供了一个熟悉的 API,甚至还有一个可爱的滑板和珍珠奶茶的熊猫吉祥物!

然而,在尝试过后,我发现它不适合我。我的一些问题是琐碎的/肤浅的;例如,Panda CSS 生成了一堆 东西,这些东西使项目文件变得混乱。这对我来说有点乱,但最终并不是一个重大问题。

对我来说,更大的问题是 Panda CSS 缺少一个关键功能。我们无法交叉引用组件。

通过一个例子来解释会更容易理解。在这个博客上,我有一个 TextLink 组件,它是 Next.js 的 Link 组件的一个样式包装器。默认情况下,它看起来像这样:

然而,相同的组件具有某些 上下文 样式。例如,当 TextLinkAside 中时,它看起来像这样:

这是一个示例链接

我使用 Aside 组件来显示辅助 / 额外信息。我发现默认的 TextLink 样式在这种情况下不太适用,因此我想应用一些覆盖样式。

这是我们如何在 styled-components 中表达这种关系的:

import Link from 'next/link';

import { AsideWrapper } from '@/components/Aside';

const TextLink = styled(Link)`
  color: var(--color-primary);
  text-decoration: none;

  ${AsideWrapper} & {
    color: inherit;
    text-decoration: underline;
  }
`;

& 字符最近作为官方嵌套语法的一部分添加到 CSS 语言中,但多年来一直是 CSS 预处理器和工具的一个惯例。在 styled-components 中,它求值为当前选择器。

在渲染此代码时,生成的 CSS 将类似于以下内容:

.textlink_abc123 {
  color: var(--color-primary);
  text-decoration: none;
}

.aside_def456 .textlink_abc123 {
  color: inherit;
  text-decoration: underline;
}

当我使用 CSS 时,我尽量遵循一个规则:特定组件的所有样式都应该写在一个地方。 我不应该在整个应用程序中进行寻宝,以找到可能适用于给定元素的所有不同 CSS!

这就是 Tailwind 如此强大的地方之一;所有样式都放在元素本身上。我们不必担心其他组件“伸手进来”并为它不拥有的元素添加样式。

这种模式就像该思想的加强版。 我们不仅列出了所有默认情况下适用于 TextLink 的样式,还列出了上下文适用的样式。它们都在一个地方,用反引号括起来。

遗憾的是,这种模式在 Panda CSS 中不起作用。在 Panda CSS 中,我们唯一标识的是 CSS 声明,而不是元素本身,因此无法表达这些类型的关系。

如果你对这种模式不感兴趣,那么 Panda CSS 可能是你的应用程序的一个好选择!但对我来说,这是一个不可接受的问题。

styled-components 的快乐之路

如果你想了解更多关于这种“上下文样式”模式

的信息,我在我的博客文章 “styled-components 的快乐之路” 中详细介绍了它。这是我在多年的 styled-components 使用中学到的模式和技巧的集合。

Pigment CSS

最受欢迎的 React 组件库之一,Material UI,是建立在 Emotion 之上的。他们的开发团队一直在处理与 RSC 兼容性相关的所有问题,他们决定采取一些行动。

他们最近开源了一个新库。它叫做 Pigment CSS。其 API 目前应该看起来非常熟悉:

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

export default function Homepage() {
  return (
    <BigRedButton>
      Click me!
    </BigRedButton>
  );
}

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

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

事实上,它使用了一个叫做 WyW-in-JS(“What you Want in JS”)的低级工具。这个工具是从 Linaria 代码库演变而来的,它隔离了“编译为 CSS 模块”的业务逻辑,并使其通用化,以便像 Pigment CSS 这样的库可以在其之上构建自己的 API。

老实说,这对我来说感觉就是完美的解决方案。CSS 模块已经经过了如此多的测试和优化。从迄今为止我所看到的情况来看,Pigment CSS 很棒,性能和开发体验都很好。

Material UI 的下一个主要版本将支持 Pigment CSS,计划最终放弃对 Emotion/styled-components 的支持。因此,Pigment CSS 很可能成为最广泛使用的 CSS-in-JS 库之一。Material UI 每周在 NPM 上下载量约为 React 的 ~500 万次,大约是 React 本身的 1/5!

现在仍然很早;Pigment CSS 几周前才开源。但是团队正在为这个项目投入大量资源。我迫不及待地想看到事情的发展!

列表还在继续

除了我们到目前为止所涵盖的库之外,生态系统中还有许多其他正在做有趣事情的项目。以下是我正在关注的一些其他项目:

  • next-yak — 由瑞士最大的电子商务零售商的开发人员创建,next-yak 是一个编译时 CSS-in-JS 库,旨在尽可能接近于 styled-components 的即插即用替代品,重新实现了许多其次 API。
  • Kuma UI — 这个库正在尝试一种相当雄心勃勃的“混合”设计,其中大多数样式在编译时提取,但同时还为 Client Component 提供了运行时。
  • Parcel macros — Parcel 是一个捆绑器,最近实现了“宏”,这是一个可以用来构建各种东西的工具,包括编译时 CSS-in-JS 库。令人惊讶的是,这个功能并不是特定于 Parcel 的,可以在 Next.js 中使用!

前进的道路

好了,我们已经探讨了大量的选项,但问题仍然存在:如果你有一个使用“传统”CSS-in-JS库的生产应用程序,你到底应该怎么做?

有点违反直觉,但在许多情况下,我实际上认为你并不需要做任何事情。 😅

很多在线讨论使人感觉好像你 不能 在现代 React / Next.js 应用程序中使用 styled-components,或者这样做会有很大的性能损失。但事实并非如此。

很多人混淆了 RSC(React Server Components)和 SSR(服务器端渲染)。服务器端渲染仍然完全按照以往的方式工作,它不受任何这些问题的影响。如果你迁移到 Next 的 App Router 或另一个 RSC 实现,你的应用程序不应该变慢。事实上,它可能会变得更快!

从性能的角度来看,RSC 和零运行时 CSS 库的主要好处是 TTI,“交互时间”。这是 UI 展示给用户和 UI 完全可交互之间的延迟。如果忽视它,可能会产生糟糕的用户体验;人们会开始点击期望它们能起作用,但什么都不会发生,因为应用程序仍在水化的过程中。

因此,如果你的应用程序现在需要很长时间才能水化,迁移到零运行时 CSS 库可能会对用户体验产生积极影响。但如果你的应用程序已经有了一个可靠的 TTI,你的用户可能不会从这次迁移中获益。

我觉得在许多情况下,最大的问题是FOMO(失去机会恐惧症)。 作为开发者,我们想要使用最新最好的工具。知道我们没有从新的优化中获得太多好处,添加一堆 "use client" 指令并不好玩。但这真的是进行大规模迁移的一个引人注目的原因吗?

我正在做什么

我维护两个主要的生产应用程序:这个博客,以及我用于交互式课程的课程平台(JavaScript 开发者的 CSSReact 的乐趣)。

我的课程平台仍然使用 Next.js Pages Router 与 styled-components,并且我暂时没有计划迁移它。我对它提供的用户体验感到满意,我认为迁移可能不会带来显著的性能优势。

我的博客目前也是使用 Next.js Pages Router 与 styled-components,尽管我正在将其迁移到使用 Next.js App Router。我选择使用 Linaria + next-with-linaria,至少目前是这样。当 Pigment CSS 更成熟一点时,我计划切换过去。

React Server Components 确实 非常酷。 React/Vercel 团队在重新思考 React 在服务器上的工作方式方面做得非常出色。但老实说,尽管已经标记为“稳定”,App Router 仍然远没有 Pages Router 成熟,还存在一些问题。

如果你对应用程序的性能感到满意,我认为你不应该感到任何更新/迁移的紧迫性 ❤️。你目前的技术栈将继续正常工作,几年后,你可以回来看看情况如何。

分享于 2024-04-20

访问量 19

预览图片