如何在 React 中使用 Storybook

2025-05-28

如何在 React 中使用 Storybook

简化您的 UI 组件开发工作流程

Storybook 是什么?我为什么要使用它?

来自Storybook 网站本身:

Storybook 是一个用于独立开发 UI 组件的开源工具

可重用组件

React 允许我们编写所谓的“可重用组件”。如果你不知道什么是可重用组件,可以想象一下按钮的例子:

  • 你可以有不同的变体:
    • primary带有红色背景的按钮
    • secondary具有绿色背景的按钮
  • disabled您还可以有不同的状态:如果表单当前正在发送,则按钮可以处于

在 React 中,处理这个问题的一个非常简单的方法是使用一个Button采用不同参数的组件:

  • 一个名为的道具disabled,它要么truefalse
  • 一个叫做 的道具variant,它要么是 ,primary要么是secondary

但是假设你正在编写这段代码,并且想看看它是什么样子。通常的方法是进入应用程序中的一个页面,然后在中间的某个位置添加按钮,将 props 传递给它,然后看看它是什么样子。

故事书

这就是 Storybook 的用武之地:它基本上允许你并行运行第二个应用程序,你可以在其中使用 Button 组件,而无需将其包含在你的应用程序中。你可以独立开发你的组件

现在,假设支持团队的某个人正在与一位无法登录的客户交谈。他们来找你并问:“嘿,你能给我看一下这个错误屏幕吗?”

如果没有 Storybook,答案就需要启动应用程序、尝试重播用户的操作、阅读一些代码来了解如何使其出现等等。

使用 Storybook,您只需在搜索栏中输入“错误屏幕”,即可立即看到它!

在 React 应用中设置 Storybook

此时你需要一个 React 应用。如果你还没有,可以克隆这个,或者按照这篇文章的步骤创建一个。本教程假设你使用create-react-app

Storybook 让一切设置变得非常简单。在终端中,只需运行:

npx -p @storybook/cli sb init
Enter fullscreen mode Exit fullscreen mode

这将基本上检查您package.json以确定您正在使用的框架,然后为您的项目生成正确的配置。

该命令应该已经更新了你的package.json脚本,并添加了:

"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public"
Enter fullscreen mode Exit fullscreen mode

第一个是我们感兴趣的。运行:

npm run storybook
Enter fullscreen mode Exit fullscreen mode

这应该会在你的浏览器中打开类似这样的内容(如果没有,只需导航至localhost:9009):
storybook-home.png

让我们看一下这里的界面:

  • 左侧是侧边栏:您可以在这里找到您的组件。点击Button,看看那里有什么!
  • 在底部,有一个类似控制台的东西:这实际上是“插件”部分。Storybook 有很多插件,可以提升你在开发组件时的体验:动态更改 props、记录输出、切换语言等等。

那么这些组件从何而来?当我们安装 Storybook 时,它会生成这些“演示”故事。它们位于src/stories/index.js

import React from "react";

import { storiesOf } from "@storybook/react";
import { action } from "@storybook/addon-actions";
import { linkTo } from "@storybook/addon-links";
// Importing the demo components from storybook itself
import { Button, Welcome } from "@storybook/react/demo";

storiesOf("Welcome", module).add("to Storybook", () => (
  <Welcome showApp={linkTo("Button")} />
));

storiesOf("Button", module)
  .add("with text", () => (
    <Button onClick={action("clicked")}>Hello Button</Button>
  ))
  .add("with some emoji", () => (
    <Button onClick={action("clicked")}>
      <span role="img" aria-label="so cool">
        😀 😎 👍 💯
      </span>
    </Button>
  ));
Enter fullscreen mode Exit fullscreen mode

将它们添加到 Storybook 的魔力在于.storybook/config.js

import { configure } from '@storybook/react';

function loadStories() {
  require('../src/stories');
}

configure(loadStories, module);
Enter fullscreen mode Exit fullscreen mode

写下你的第一个故事

配置 Storybook

我们要做的第一件事就是删除这些演示故事,并改变故事在 Storybook 中的加载方式。把那个src/stories/文件夹也删掉吧,我们不需要它。

将所有内容替换.storybook/config.js为:

import { configure } from '@storybook/react';

const req = require.context('../src/', true, /\.stories\.js$/);

function loadStories() {
  req.keys().forEach(filename => req(filename));
}

configure(loadStories, module);
Enter fullscreen mode Exit fullscreen mode

这将告诉 Storybook 选取所有以 结尾的文件.stories.js。你会发现,(通常)将故事放在靠近要测试的组件的位置要容易得多。

一个简单的按钮组件

现在让我们写第一个故事。如果你使用我的 GitHub 示例,请前往src/components/atoms并创建以下文件:

|––atoms
  |––Button
    |––index.js
    |––Button.js
    |––Button.stories.js
Enter fullscreen mode Exit fullscreen mode

Button.js

import React from "react";

const Button = props => {
  const { variant, disabled, children } = props;

  // This is the default style
  let backgroundColor = "white";
  let color = "black";

  // Which variant do we want?
  switch (variant) {
    case "primary":
      backgroundColor = "red";
      color = "white";
      break;
    case "secondary":
      backgroundColor = "green";
      color = "white";
      break;
    default:
      break;
  }

  // Let's build the style based on the variant
  // We also add properties depending on the `disabled` state
  const style = {
    backgroundColor,
    color,
    cursor: disabled ? "not-allowed" : "pointer",
    opacity: disabled ? 0.5 : 1
  };

  return (
    <button disabled={disabled} style={style}>
      {children}
    </button>
  );
};

export default Button;
Enter fullscreen mode Exit fullscreen mode

Button.stories.js

import React from "react";
import { storiesOf } from "@storybook/react";
import Button from "./Button";

// You can see this as "folders" in Storybook's sidebar
const stories = storiesOf("atoms/Button", module);

// Every story represents a state for our Button component
stories.add("default", () => <Button>Button</Button>);
stories.add("default disabled", () => <Button disabled>Button</Button>);
stories.add("primary", () => <Button variant="primary">Button</Button>);
// Passing a prop without a value is basically the same as passing `true`
stories.add("primary disabled", () => (
  <Button variant="primary" disabled>
    Button
  </Button>
));
stories.add("secondary", () => <Button variant="secondary">Button</Button>);
stories.add("secondary disabled", () => (
  <Button variant="secondary" disabled>
    Button
  </Button>
));
Enter fullscreen mode Exit fullscreen mode

index.js

// This allows us to import `src/components/Button` directly,
// without having to go all the way to `src/components/Button/Button`
export { default } from "./Button";
Enter fullscreen mode Exit fullscreen mode

现在再次转到 Storybook,看看你的故事创建了什么:
故事书按钮.png

浏览我们创作的不同故事,并注意按钮是如何变化的。

一切都是实时的

Storybook 拥有非常快速的热重载机制。这意味着你可以直接在组件中将“红色”改为蓝色,Storybook 就会立即重新编译你的 Storybook,将你的更改纳入其中!

附加组件

Storybook 提供了各种非常方便的插件,帮助我们自信地独立开发组件。让我们来设置其中的一些。

附加信息

有时,当你浏览 Storybook 时,你会想阅读特定故事的代码。这个info插件正是用来做到这一点的。安装方法如下:

npm i -D @storybook/addon-info
Enter fullscreen mode Exit fullscreen mode

通过编辑全局添加插件.storybook/config.js

...
import { addDecorator } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';

addDecorator(withInfo);
...
Enter fullscreen mode Exit fullscreen mode

show info这将在您的故事的右上角添加一个按钮,其中显示有关该故事的一些信息:
插件信息.png

附加操作

当组件中发生某个操作时,记录日志会很有用。例如,假设我们修改了 Button 组件,使其接受一个onClickprop:

...
const Button = props => {
  const { variant, disabled, children, onClick } = props;
  ...
return (
    <button onClick={onClick} disabled={disabled} style={style}>
      {children}
    </button>
  );
Enter fullscreen mode Exit fullscreen mode

我们如何测试点击按钮是否会触发onClick处理程序?Storybook 提供了一个已安装的官方插件,可以帮助解决这个问题。在你的故事中,导入action,然后添加以下onClick属性:

import { action } from "@storybook/addon-actions";
...
stories.add("default", () => (
  <Button onClick={action("clicked!")}>Button</Button>
));
stories.add("default disabled", () => (
  <Button onClick={action("clicked!")} disabled>
    Button
  </Button>
));
stories.add("primary", () => (
  <Button onClick={action("clicked!")} variant="primary">
    Button
  </Button>
));
stories.add("primary disabled", () => (
  <Button onClick={action("clicked!")} variant="primary" disabled>
    Button
  </Button>
));
stories.add("secondary", () => (
  <Button onClick={action("clicked!")} variant="secondary">
    Button
  </Button>
));
stories.add("secondary disabled", () => (
  <Button onClick={action("clicked!")} variant="secondary" disabled>
    Button
  </Button>
));
Enter fullscreen mode Exit fullscreen mode

现在,每次单击按钮时,Storybook 都会打印一条新日志:
点击.png

附加旋钮

现在,我们需要为同一个组件编写许多不同的故事,因为我们需要处理每个道具组合。如果我们可以在 Storybook 中实时编辑道具会怎么样?解决方案是addon-knobs,它极大地简化了我们编写故事的方式。

首先,使用以下命令安装插件:

npm i -D @storybook/addon-knobs
Enter fullscreen mode Exit fullscreen mode

然后,将其添加到.storybook/addons.js

import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
// add this line
import '@storybook/addon-knobs/register';
Enter fullscreen mode Exit fullscreen mode

并使用新插件重写您的故事:

import React from "react";
import { storiesOf } from "@storybook/react";
import Button from "./Button";
import { action } from "@storybook/addon-actions";
// add this line
import { withKnobs, select, boolean } from "@storybook/addon-knobs";

// You can see this as "folders" in Storybook's sidebar
const stories = storiesOf("atoms/Button", module);

// add this line
stories.addDecorator(withKnobs);

// ---- add this block
const variantOptions = {
  none: "",
  primary: "primary",
  secondary: "secondary"
};
// ----

stories.add("with knobs", () => (
  <Button
    onClick={action("clicked!")}
    // ---- and this one
    // syntax is (name, options, default)
    variant={select("variant", variantOptions, "")}
    // syntax is (name, default)
    disabled={boolean("disabled", false)}
    // ----
  >
    Button
  </Button>
));
Enter fullscreen mode Exit fullscreen mode

现在,当您进入故事时,在附加组件部分,您可以看到一个名为旋钮的新选项卡,您可以通过使用它们来更改组件的道具:
旋钮.png

更酷的是,它addon-info与这些道具保持同步!
旋钮同步.png

快照测试

由于 React 组件可复用,因此一个组件被包含在许多其他组件中是很常见的。跟踪组件成为依赖项的所有位置并评估微小更改的影响可能会变得非常困难。Storybook结合jest(create-react-app 已自带) ,可以轻松设置快照测试。

首先,安装所需的依赖项:

npm i -D @storybook/addon-storyshots react-test-renderer require-context.macro
Enter fullscreen mode Exit fullscreen mode

然后,在.storybook/config.js

import requireContext from 'require-context.macro';

// const req = require.context('../src', true, /\.stories\.js$/); <-- replaced
const req = requireContext('../src', true, /\.stories\.js$/);
Enter fullscreen mode Exit fullscreen mode

在中创建以下结构src

|––test
  |––storyshots.test.js
Enter fullscreen mode Exit fullscreen mode

并将其添加到storyshots.test.js

import initStoryshots from '@storybook/addon-storyshots';

initStoryshots();
Enter fullscreen mode Exit fullscreen mode

最后,运行npm run test(或npm test简写)。这将在 处创建一个快照文件src/test/__snapshots__/storyshots.test.js.snap

现在,当你运行测试时,Storybook 将渲染每个故事并将其与之前创建的快照进行比较。尝试在 Button 组件中更改某些内容,然后再次运行测试,例如:

switch (variant) {
    case "primary":
      backgroundColor = "red";
      color = "white";
      break;
    case "secondary":
      // change this...
      //backgroundColor = "green";
      // ...into this
      backgroundColor = "gray";
      color = "white";
      break;
    default:
      break;
  }
Enter fullscreen mode Exit fullscreen mode

Jest 会抱怨快照不正确,并给你一份非常有用的报告:
快照失败.png

您可以检查更改,并确定是否破坏了某些内容,或者每个更改是否都是有意为之。如果一切正常,您可以使用以下命令更新快照:

npm run test -- -u
Enter fullscreen mode Exit fullscreen mode

在开发一个重要功能后运行快照测试对于审查您所做的事情以及更改的影响非常有帮助。

在GitHub上查找最终代码

文章来源:https://dev.to/tducasse/how-to-use-storybook-with-react-10g1
PREV
React 自定义 Hook - useFetch
NEXT
我如何教授 Git