如何在 React 中使用 Storybook
简化您的 UI 组件开发工作流程
Storybook 是什么?我为什么要使用它?
来自Storybook 网站本身:
Storybook 是一个用于独立开发 UI 组件的开源工具
可重用组件
React 允许我们编写所谓的“可重用组件”。如果你不知道什么是可重用组件,可以想象一下按钮的例子:
- 你可以有不同的变体:
primary
带有红色背景的按钮secondary
具有绿色背景的按钮
disabled
您还可以有不同的状态:如果表单当前正在发送,则按钮可以处于
在 React 中,处理这个问题的一个非常简单的方法是使用一个Button
采用不同参数的组件:
- 一个名为的道具
disabled
,它要么true
是false
- 一个叫做 的道具
variant
,它要么是 ,primary
要么是secondary
。
但是假设你正在编写这段代码,并且想看看它是什么样子。通常的方法是进入应用程序中的一个页面,然后在中间的某个位置添加按钮,将 props 传递给它,然后看看它是什么样子。
故事书
这就是 Storybook 的用武之地:它基本上允许你并行运行第二个应用程序,你可以在其中使用 Button 组件,而无需将其包含在你的应用程序中。你可以独立开发你的组件。
现在,假设支持团队的某个人正在与一位无法登录的客户交谈。他们来找你并问:“嘿,你能给我看一下这个错误屏幕吗?”
如果没有 Storybook,答案就需要启动应用程序、尝试重播用户的操作、阅读一些代码来了解如何使其出现等等。
使用 Storybook,您只需在搜索栏中输入“错误屏幕”,即可立即看到它!
在 React 应用中设置 Storybook
此时你需要一个 React 应用。如果你还没有,可以克隆这个,或者按照这篇文章的步骤创建一个。本教程假设你使用create-react-app。
Storybook 让一切设置变得非常简单。在终端中,只需运行:
npx -p @storybook/cli sb init
这将基本上检查您package.json
以确定您正在使用的框架,然后为您的项目生成正确的配置。
该命令应该已经更新了你的package.json
脚本,并添加了:
"storybook": "start-storybook -p 9009 -s public",
"build-storybook": "build-storybook -s public"
第一个是我们感兴趣的。运行:
npm run storybook
这应该会在你的浏览器中打开类似这样的内容(如果没有,只需导航至localhost:9009
):
让我们看一下这里的界面:
- 左侧是侧边栏:您可以在这里找到您的组件。点击
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>
));
将它们添加到 Storybook 的魔力在于.storybook/config.js
:
import { configure } from '@storybook/react';
function loadStories() {
require('../src/stories');
}
configure(loadStories, module);
写下你的第一个故事
配置 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);
这将告诉 Storybook 选取所有以 结尾的文件.stories.js
。你会发现,(通常)将故事放在靠近要测试的组件的位置要容易得多。
一个简单的按钮组件
现在让我们写第一个故事。如果你使用我的 GitHub 示例,请前往src/components/atoms
并创建以下文件:
|––atoms
|––Button
|––index.js
|––Button.js
|––Button.stories.js
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;
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>
));
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";
浏览我们创作的不同故事,并注意按钮是如何变化的。
一切都是实时的
Storybook 拥有非常快速的热重载机制。这意味着你可以直接在组件中将“红色”改为蓝色,Storybook 就会立即重新编译你的 Storybook,将你的更改纳入其中!
附加组件
Storybook 提供了各种非常方便的插件,帮助我们自信地独立开发组件。让我们来设置其中的一些。
附加信息
有时,当你浏览 Storybook 时,你会想阅读特定故事的代码。这个info
插件正是用来做到这一点的。安装方法如下:
npm i -D @storybook/addon-info
通过编辑全局添加插件.storybook/config.js
:
...
import { addDecorator } from '@storybook/react';
import { withInfo } from '@storybook/addon-info';
addDecorator(withInfo);
...
show info
这将在您的故事的右上角添加一个按钮,其中显示有关该故事的一些信息:
附加操作
当组件中发生某个操作时,记录日志会很有用。例如,假设我们修改了 Button 组件,使其接受一个onClick
prop:
...
const Button = props => {
const { variant, disabled, children, onClick } = props;
...
return (
<button onClick={onClick} disabled={disabled} style={style}>
{children}
</button>
);
我们如何测试点击按钮是否会触发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>
));
现在,每次单击按钮时,Storybook 都会打印一条新日志:
附加旋钮
现在,我们需要为同一个组件编写许多不同的故事,因为我们需要处理每个道具组合。如果我们可以在 Storybook 中实时编辑道具会怎么样?解决方案是addon-knobs,它极大地简化了我们编写故事的方式。
首先,使用以下命令安装插件:
npm i -D @storybook/addon-knobs
然后,将其添加到.storybook/addons.js
:
import '@storybook/addon-actions/register';
import '@storybook/addon-links/register';
// add this line
import '@storybook/addon-knobs/register';
并使用新插件重写您的故事:
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>
));
现在,当您进入故事时,在附加组件部分,您可以看到一个名为旋钮的新选项卡,您可以通过使用它们来更改组件的道具:
快照测试
由于 React 组件可复用,因此一个组件被包含在许多其他组件中是很常见的。跟踪组件成为依赖项的所有位置并评估微小更改的影响可能会变得非常困难。Storybook结合jest(create-react-app 已自带) ,可以轻松设置快照测试。
首先,安装所需的依赖项:
npm i -D @storybook/addon-storyshots react-test-renderer require-context.macro
然后,在.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$/);
在中创建以下结构src
:
|––test
|––storyshots.test.js
并将其添加到storyshots.test.js
import initStoryshots from '@storybook/addon-storyshots';
initStoryshots();
最后,运行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;
}
您可以检查更改,并确定是否破坏了某些内容,或者每个更改是否都是有意为之。如果一切正常,您可以使用以下命令更新快照:
npm run test -- -u
在开发一个重要功能后运行快照测试对于审查您所做的事情以及更改的影响非常有帮助。
在GitHub上查找最终代码。
文章来源:https://dev.to/tducasse/how-to-use-storybook-with-react-10g1