Storybook 帮助您构建任何组件,从小的“原子”组件到组合页面。但是,当您沿着组件层次结构向上移动到页面级别时,您将面临更多复杂性。
在 Storybook 中构建页面有很多方法。以下是常见模式和解决方案。
- 纯表现型页面。
- 连接组件(例如,网络请求、上下文、浏览器环境)。
BBC、卫报和 Storybook 维护人员团队本身构建纯表现型页面。如果您采用这种方法,则不需要做任何特殊操作即可在 Storybook 中呈现您的页面。
将组件完全写成表现型直到屏幕级别,这很简单。这使得在 Storybook 中展示变得容易。想法是,您在 Storybook 之外的应用程序中,在一个单独的包装器组件中执行所有混乱的“连接”逻辑。您可以在 Storybook 入门教程的 数据 章节中看到此方法的示例。
优点
- 一旦组件处于这种形式,故事就很容易编写。
- 故事的所有数据都编码在故事的参数中,这与 Storybook 工具的其他部分配合良好(例如 控件)。
缺点
-
您的现有应用程序可能没有按这种方式构建,改变它可能很困难。
-
在一个地方获取数据意味着您需要将其向下传递到使用它的组件。这在一个组合一个大型 GraphQL 查询(例如)的页面中可能是自然的,但其他数据获取方法可能使其不再合适。
-
如果您想在屏幕上的不同位置增量加载数据,它灵活性较差。
当您以这种方式构建屏幕时,复合组件的输入通常是其渲染的各种子组件的输入的组合。例如,如果您的屏幕渲染一个页面布局(包含当前用户的详细信息)、一个标题(描述您正在查看的文档)和一个列表(子文档),则屏幕的输入可能包括用户、文档和子文档。
在这种情况下,使用 参数组合 来根据子组件的故事构建页面的故事是很自然的
当各种子组件导出一个复杂的不同故事列表时,这种方法是有益的。您可以选择构建屏幕级故事的真实场景,而无需重复自己。您的故事维护负担很小,因为您重用数据并采用了不重复自己 (DRY) 的理念。
连接组件是依赖于外部数据或服务的组件。例如,完整的页面组件通常是连接组件。当您在 Storybook 中渲染连接组件时,您需要模拟组件依赖的数据或模块。您可以通过多种方式做到这一点。
组件可以依赖导入到组件文件中的模块。这些模块可以来自外部包或项目内部。在 Storybook 中渲染这些组件或测试它们时,您可能希望模拟这些模块来控制它们的行为。
对于进行网络请求的组件(例如,从 REST 或 GraphQL API 获取数据),您可以在故事中模拟这些请求。
组件可以从上下文提供者接收数据或配置。例如,一个样式化组件可能会从 ThemeProvider 中访问其主题,或者 Redux 使用 React 上下文来提供组件访问应用程序数据。您可以在故事中模拟提供者及其提供的价值,并用它来包装您的组件。
可以通过 props 或 React 上下文传递连接的“容器”组件的依赖项来完全避免模拟它们。但是,这需要严格的容器和表示组件逻辑分离。例如,如果有一个组件负责数据获取逻辑和渲染 DOM,则需要像前面描述的那样模拟它。
在表示组件中导入和嵌入容器组件很常见。但是,正如我们之前发现的那样,我们可能需要模拟它们的依赖项或导入才能在 Storybook 中渲染它们。
这不仅会很快演变成一项繁琐的任务,而且模拟使用本地状态的容器组件也很有挑战性。因此,与其直接导入容器,不如创建一个提供容器组件的 React 上下文。它允许您像往常一样自由地嵌入容器组件,在组件层次结构的任何级别,而不用担心随后模拟它们的依赖项;因为我们可以用模拟的表示组件替换容器本身。
建议将上下文容器划分到应用程序中特定页面或视图之上。例如,如果您有一个名为 ProfilePage
的组件,则可能设置如下文件结构:
ProfilePage.js
ProfilePage.stories.js
ProfilePageContainer.js
ProfilePageContext.js
💡设置一个“全局”容器上下文(可能命名为 GlobalContainerContext
)对于可能渲染在应用程序每个页面的容器组件,并将它们添加到应用程序的顶层也很有用。虽然可以将每个容器都放到这个全局上下文中,但它只应该提供全局所需的容器。
让我们看一个这种方法的示例实现。
首先,创建一个 React 上下文,并将其命名为 ProfilePageContext
。它除了导出一个 React 上下文之外什么也不做。
ProfilePageContext.js|jsx
ProfilePage
是我们的表示组件。它将使用 useContext
hook 从 ProfilePageContext
中检索容器组件。
在 Storybook 的上下文中,我们将提供模拟的对应项,而不是通过上下文提供容器组件。在大多数情况下,这些组件的模拟版本通常可以直接从其关联的故事中借用。
ProfilePage.stories.js|jsx
ℹ️如果同一个上下文适用于所有 ProfilePage
故事,我们可以使用 装饰器。
现在,在应用程序的上下文中,您需要通过用 ProfilePageContext.Provider
包装它来为 ProfilePage
提供它需要的所有容器组件。
例如,在 Next.js 中,这将是您的 pages/profile.js
组件。
如果您已经设置了 GlobalContainerContext
,则需要在 Storybook 的 preview.js
中设置一个装饰器,以便为所有故事提供上下文。例如