
Storybook for React Server Components
通过升级到 Storybook 8.0 alpha 版本在 Storybook 中使用 RSC

React 服务器组件 (RSC) 是一种用于 React Web UI 的新编程模型。与传统的 React “客户端”组件不同,它们仅在服务器上渲染。这带来了各种性能和安全优势,但这也与我们今天使用的 React 工具和库大相径庭。
受影响最大的领域之一是组件驱动的开发和测试。Storybook、Testing Library 和 Playwright/Cypress 组件测试等工具都假定用户的组件是在浏览器(或 JSDom)中渲染的。但对于服务器组件来说,情况不再如此。
这就产生了一个问题:什么是独立的服务器组件开发和测试的含义?
今天,我很高兴在 Storybook 的 Next.js 框架中发布 RSC 支持,作为对这个问题的实验性回答。这是一个纯粹的客户端实现,使其与整个 Storybook 插件和集成生态系统兼容。
继续阅读,了解它是如何工作的、如何使用它以及今天如何尝试它!
服务器来自火星,客户端来自金星
RSC 与传统客户端组件有两个主要区别,这两个区别都在以下示例中体现:
// ApiCard.tsx
import { ComponentProps } from 'react';
import { Card } from './Card';
import { findById } from './db';
export async function DbCard({ id }: {id: number}) {
let props;
try {
const contact = await findById(id);
props = { state: 'success', contact };
} catch (e) {
props = { state: 'error' };
}
return <Card {...props} />;
}- 第一个区别是我们的组件是
async的,这在客户端不支持。 - 第二个区别是我们的组件可以直接访问 Node.js 代码,在本例中是包装了已验证的数据库连接的
findById函数。
RSC 在底层做了很多工作来实现这两个区别。这些代码只在服务器上运行,并生成一个静态的类 JSON 结构,该结构被流式传输到客户端。
Storybook 是一个纯客户端应用程序。它生成纯 HTML/CSS/JS 的静态构建,没有 Node.js 在其中!因此,支持 RSC 需要弄清楚如何让 RSC 在客户端渲染,或者重新设计 Storybook 的服务器端。
我们开始专注于客户端方法。我们希望尽量减少对用户的 we 影响,他们已经编写了数百万个故事和数百个插件,所有这些都基于当前的架构。
那么,它是如何工作的呢?
与 async 保持同步
让 RSC 在客户端渲染的第一个挑战是配置如何支持 async 组件。事实证明,这在 Next.js 的 canary React 版本中已经(非官方地)受支持。特别感谢 JamesManningR 和 julRuss,他们贡献了这个简单的解决方案!
import { Suspense } from 'react';
export const ClientContact = ({ id }) => (
<Suspense><DbCard id={id} /></Suspense>
);从 Storybook 8 开始,@storybook/nextjs 可以使用 .storybook/main.js 中的 experimentalRSC 功能标志将您的故事包装在 Suspense 中。
// .storybook/main.js
export default {
features: {
experimentalRSC: true,
}
};您也可以在 @storybook/nextjs 的 7.x 版本中通过将您的 RSC 故事包装在 装饰器 中来手动完成此操作。
注意:此解决方案在其他 Storybook React 框架(例如 react-vite、react-webpack5)中尚不可用,因为它们不使用 Next.js 的 React canary 版本。希望这个限制能在下一个 React 版本中被移除。
已模拟并加载
解决 async 问题只完成了一半。我们的 DbCard 组件还引用了获取数据以填充组件的 Node.js 代码。这在浏览器中是个问题,因为浏览器无法执行 Node.js 代码!
为了解决这个问题,我们建议建立一个干净的数据访问层。这也是 RSC 的架构师推荐的最佳实践。
一旦有了数据访问层,就可以对其进行模拟,以便它可以在浏览器中运行,并且您可以精确地控制它返回的数据,以尝试不同的 UI 状态(加载、错误、成功等)。
您可以使用模块模拟或网络模拟来模拟数据访问层,这两者在 Storybook 中都受支持。
模块:有一个社区插件 storybook-addon-module-mock,它提供 jest.mock 风格的模拟(仅适用于 Webpack 项目)。您也可以使用 webpack/vite 别名来实现更简单但功能有限的解决方案。我们计划在 Storybook 的未来版本中提供更符合人体工程学的模块模拟。
网络 API:要模拟网络请求,我们推荐 Mock Service Worker (msw)。Storybook 还支持许多其他网络和 GraphQL 模拟插件。
回到我们的示例,使用 storybook-addon-module-mock 的故事可能如下所示:
// DbCard.stories.js
import { StoryObj, Meta } from '@storybook/react';
import { createMock } from 'storybook-addon-module-mock';
import { DbCard } from './DbCard';
import * as db from './db';
export default { component: DbCard };
export const Success {
args: { id: 1 },
parameters: {
moduleMock: {
mock: () => {
const mock = createMock(db, 'findById');
mock.mockReturnValue(Promise.resolve({
name: 'Beyonce',
img: 'https://blackhistorywall.files.wordpress.com/2010/02/picture-device-independent-bitmap-119.jpg',
tel: '+123 456 789',
email: 'b@beyonce.com'
}))
return [mock];
},
},
},
}完整演示:API + 模块模拟
有关上述所有示例,包括模块模拟的数据库版本和 MSW2 模拟的 API 版本,请查看我们的完整 RSC 演示 Storybook 或其 GitHub 仓库。

有什么弊端?
在本篇文章中,我们成功地为我们的第一个 RSC 编写了 Storybook 中的故事,并展示了所有这些是如何在后台实现的。
这一切都相当直接,但这种方法存在局限性:
- 保真度。纯客户端实现与您的应用程序中运行的服务器端流式 RSC 实现有很大不同。
- 便利性。这里的模拟解决方案肯定还有改进的空间。我们目前的模块模拟解决方案不仅冗长,而且与 Storybook 的参数/控件配合不佳。
我们计划在后续迭代中解决这两个限制,因此我们将此解决方案标记为实验性。
立即使用 Storybook 为 RSC 🎊
要为 RSC 使用 Storybook,请将您的 Storybook 升级到 8.0-alpha。
npx storybook@next upgrade --prerelease
然后,在您的 .storybook/main.ts 中启用实验性功能。
// .storybook/main.js
export default {
features: {
experimentalRSC: true,
},
};
有关更多信息,请参阅 @storybook/nextjs 文档。
这是我们关于 Storybook 8.0(我们的下一个主要版本)内容的系列文章的第一篇,在接下来的几个月里,我们将有更多内容。请在社交媒体上关注我们或注册 Storybook 新闻通讯,以了解下一个版本的最新消息!
Storybook 现在支持 React 服务器组件!
— Storybook (@storybookjs) 2023 年 12 月 13 日
了解它是如何工作的,并在 Storybook 8.0 alpha 版本中立即尝试 🔥https://#/lOuql2kKPq