随着React 生态系统的不断发展,尤其是Next.js 转向"服务器优先"方法,使用 CSS-in-JS 解决方案(如Emotion和styled-components)的应用程序面临着重大挑战。这些 CSS-in-JS 库本质上与新的 React 范式不兼容。
虽然有人可能会认为这些库可以更新以适应新的服务器优先方向,但重要的是要记住它们是由无偿开源贡献者维护的。期望他们彻底改造他们的库以适应 React 的变化是一项艰巨且可能不切实际的任务。
让我们看看为什么一些 CSS-in-JS 解决方案无法在新的服务器优先世界中发挥作用,然后我将向您介绍一种替代方案Linaria,它使用与 styled-components 几乎相同的 API。
RSC范式
这将变成一个冗长的解释,但了解 React 过去的工作方式(React Server Components 之前)以及现在的工作方式(RSC 之后)非常重要。
Pre-RSC:服务器和客户端
那么让我们从头开始。在 RSC 之前,React 在服务器和客户端上都渲染了一个"组件"。React始终在服务器上渲染 HTML 页面的静态部分,并将其发送到客户端进行"hydrated"。hydrated 阶段是客户端 JavaScript,负责处理 refs 或附加事件处理程序,这通常允许交互。
如果觉得两次执行这项工作有点愚蠢 --- 一次在服务器上,然后在客户端上再次重播 --- 那是因为它确实有点愚蠢。我之前曾通过比较React 的 hydration 与 Qwik 的可恢复方法讨论过这个问题,对我来说这更有意义。
RSC 后:仅限服务器
然而,在后 RSC 世界中,默认情况下所有 React 组件都是服务器专用的。React 会渲染 HTML 页面的静态部分,但不会在客户端上对其进行水合。这减少/消除了任何客户端 JavaScript。这是一种有趣的方法,因为在后 RSC 世界中,我们可以从任何组件、树中的任何级别发出服务器端请求,而不仅仅是在路由级别,这是 Next.js 以前的工作方式。
React 的新使用客户端指令
在后 RSC 世界中,您仍然可以启用客户端 JavaScript 和 React 特定方法(例如useState
、useEffect
等),但您必须明确将use client
指令添加到文件顶部,以便 React 能够以与以往相同的方式运行。
为什么 CSS-in-JS 不能与 RSC 一起使用?
为了增加一些背景信息,重要的是要了解传统的 CSS-in-JS 库是如何工作的,以便您能够理解为什么它们在后 RSC 世界中无法工作。
诸如 Emotion 和 styled-components 之类的库在运行时使用客户端 JavaScript 完成了很多繁重的工作,而我们已经确定 RSC 或"仅限服务器"的组件中不再存在这些 JavaScript。
你可能已经看到过这种效果。如果你检查任何使用 styled-components 的应用程序或网站,你会注意到那些看起来很奇怪的类名。
<html>
<body>
<a class="sc-4c0ad8fd-0 hcJJXU">
styled link
</a>
</body>
</html>
这是因为使用 styled-components,您(开发人员)无需编写 CSS 类,只需编写组件 --- 并且 styled-components(库)负责提取样式,将其转换为浏览器可以理解的 CSS 并生成一个随机但唯一的类名,然后将其应用于 DOM 元素以将两者链接在一起。
例如,您可以这样使用 styled-components 定义 HTML 锚元素以在 React 组件中使用。
// src/components/some-component/index.js
import styled from 'styled-components';
const SomeComponent = () => {
return (
<ButtonLink>
styled link
</ButtonLink>
);
}
const ButtonLink = styled.a`
background: transparent;
border-radius: 3px;
border: 1px solid var(--accent-color);
color: var(--accent-color);
display: inline-block;
margin: 0.5rem 1rem;
padding: 0.5rem 0;
transition: all 200ms ease-in-out;
width: 11rem;
`;
export default SomeComponent
当 styled-components 运行时,它会创建类名(如上所示),将其添加到 DOM 元素,并将styled.a
创建时定义的 CSS 值转换为浏览器可以理解的 CSS,例如:
.hcJJXU {
background: transparent;
border-radius: 3px;
border: 1px solid var(--accent-color);
color: var(--accent-color);
display: inline-block;
margin: 0.5rem 1rem;
padding: 0.5rem 0px;
transition: all 200ms ease-in-out 0s;
width: 11rem;
}
但正如前面提到的,这一切都发生在运行时,在客户端,使用客户端 JavaScript。
遗憾的是,由于 RSC 被设计为在服务器上呈现,然后作为 HTML 发送到客户端,它们具有特定的特性和限制 - 特别是对于客户端 JavaScript,它会阻止 styled-components 等库的工作。
一切尚未失去
针对此问题,有许多较新的解决方案,将繁重的工作从客户端转移到构建。我们在这里已经做了很多繁重的工作,将 TypeScript 转换为"浏览器 Js",因此,目前将 CSS-in-JS 用作后 RSC 世界的解决方案似乎是一个很好的解决方案。
但是,你可能会想:这是否意味着全世界的开发人员都需要styled.
使用一些新奇的 API 重写所有声明?因为那将是一场噩梦!
值得庆幸的是, Linaria的创建者(实际上自2017 年起就已存在)选择使用与 styled-components 非常相似的 API。MUI团队的Pigment CSS也遵循了类似的模式,这是个好消息。
RSC 兼容 CSS-in-JS 库如何工作?
对于 Linaria 来说,它利用了CSS 模块。如果我styled.a
再次使用该示例:
// src/components/some-component/index.js
import styled from '@linaria/react';
const SomeComponent = () => {
return (
<ButtonLink>
styled link
</ButtonLink>
);
}
const ButtonLink = styled.a`
background: transparent;
border-radius: 3px;
border: 1px solid var(--accent-color);
color: var(--accent-color);
display: inline-block;
margin: 0.5rem 1rem;
padding: 0.5rem 0;
transition: all 200ms ease-in-out;
width: 11rem;
`;
export default SomeComponent
Linaria 不会将其转换为随机但唯一的类名,而是创建一个[component name].module.css
文件并将样式声明转换为真正的 CSS 命名类,例如:
/* src/components/some-component/SomeComponent.module.css */
.ButtonLink {
background: transparent;
border-radius: 3px;
border: 1px solid var(--accent-color);
color: var(--accent-color);
display: inline-block;
margin: 0.5rem 1rem;
padding: 0.5rem 0px;
transition: all 200ms ease-in-out 0s;
width: 11rem;
};
在应用程序的构建步骤期间,.css
将向组件添加对 CSS 模块文件的导入,并将样式声明转换为底层 HTML 元素(在本例中它是一个锚元素),并添加对该类的引用:
import styles from './SomeComponent.module.css';
const SomeComponent = () => {
return (
<a className={styles.ButtonLink}>
styled link
</a>
);
}
export default SomeComponent
由于这一切都发生在构建步骤中,浏览器无需执行任何操作 --- 因此,无需客户端 JavaScript。这实际上是解决问题的一个很酷的方法,迁移所需的只是卸载 styled-components、安装 Linaria,然后执行从import styled from 'styled-components';
到 的查找和替换import styled from '@linaria/react';
。
总结
所以,当像 React 这样广泛使用的东西彻底改变其基本工作方式时,许多为其设计的开源项目也需要改变------或者,在某些不幸的情况下,被淘汰出局。在 JavaScript 领域,一切都在频繁变化,我们所能做的就是努力跟上!