人人都能读懂的故事:CSF 对阵 MDX
今天,我要讲讲Storybook v6。它是一款非常棒的工具,可以用来设计、构建、记录和测试独立的组件,并组织一个完美的组件库。
组件故事格式 (CSF)是编写故事的推荐方式,但最近 Storybook 引入了使用MDX 格式编写故事的选项,这样我们就可以使用我们都非常熟悉的格式轻松地记录我们的组件。
在这篇文章中,我将介绍两种编写故事的方法,目的是展示这两种工具的一些优点,并让您选择最适合您项目的方法。
我将使用一个简单的Avatar组件作为示例,我们将基于它来创建我们的故事。我通常使用 React 和 Style Components 构建组件库,所以今天我们也将继续使用它们。
我们的Avatar计划看起来会是这样:
import styled from "styled-components";
import PropTypes from "prop-types";
export default function Avatar({ src, size, className, alt }) {
return <Image size={size} className={className} src={src} alt={alt} />;
}
const Image = styled.img`
border-radius: 100%;
height: ${(props) => sizes[props.size]};
width: ${(props) => sizes[props.size]};
`;
const sizes = {
small: "30px",
medium: "60px",
large: "160px",
};
Avatar.propTypes = {
/**
The display src of the avatar
*/
src: PropTypes.string,
/**
The display size of the avatar
*/
size: PropTypes.oneOf(Object.keys(sizes)),
/**
Preserve className for styling
*/
className: PropTypes.string,
/**
Include alt tag for accessibility
*/
alt: PropTypes.string,
};
Avatar.defaultProps = {
size: "medium",
src: "/defaultAvatar.svg",
alt: "avatar",
};
对于 Storybook 新手来说,一个故事由一个 Canvas 和一个 Docs Block 组成。Canvas 用于显示我们渲染的组件,而 Docs Block 则是 Storybook 文档页面的基本构建模块。PropTypes稍后,我们将在 Docs Block 中使用该函数来显示组件ArgsTable及其所有属性args(props)。
组件故事格式 (CSF)
让我们从 Storybook 推荐的语法 CSF 开始。
在 CSF 中,故事被定义为 ES6 模块。因此,每个故事都由一个默认导出和一个或多个命名导出组成。
默认导出功能为我们为组件编写的所有故事提供了一个默认结构,在我们的例子中,就是组件Avatar。
// Avatar.stories.js/ts
import React from "react";
import Avatar from "../components/Avatar";
export default {
title: "Components/Avatar",
component: Avatar,
};
命名导出是一个描述组件渲染方式的函数,它看起来会像这样:
export const Default = () => <Avatar src="/defaultAvatar.svg" size="medium" />;
但我们通常构建组件库是为了编写文档,所以我们希望能够与用户故事进行交互,并检查每个用例。因此,引入Controls和Actions 插件args非常方便,这样我们就可以利用它们的优势。
Storybook 文档中提到,可以使用该.bind()方法创建一个可重用的模板,将组件传递args给每个组件对应的故事。当单个组件需要对应多个故事时,这种方法非常实用,因为它能减少代码重复。
const Template = (args) => <Avatar {...args} />;
export const Default = Template.bind({});
Default.args = {
src: "/defaultAvatar.svg",
size: "medium",
alt: "avatar",
};
export const Profile = Template.bind({});
Profile.args = {
src: "/lauraAvatar.svg",
size: "small",
alt: "user profile",
};
但如果你要和不太熟悉这种.bind()方法的设计师或其他同事密切合作,args在每个故事中传递参数也是个好办法。虽然这样会增加一些重复代码,但代码更易读,而且你也可以省去学习 JavaScript 的麻烦。
export const Default = (args) => <Avatar {...args} />;
Default.args = {
src: "/defaultAvatar.svg",
size: "medium",
alt: "avatar",
};
好了,既然我们已经讲完了这个Avatar故事,或许应该添加一些文档说明。而这正是使用 CSF 时可能会遇到一些棘手问题的地方。
要在我们的主故事中添加描述,我们需要将其插入到parameters对象中,使用parameters.docs.description.component我们的默认导出或parameters.docs.description.story命名导出。
export default {
title: "Components/Avatar",
component: Avatar,
parameters: {
component: Avatar,
componentSubtitle:
"An Avatar is a visual representation of a user or entity.",
docs: {
description: {
component: "Some description",
},
},
},
};
const Template = (args) => <Avatar {...args} />;
export const Default = Template.bind({});
Default.args = {
src: "/defaultAvatar.svg",
size: "medium",
alt: "avatar",
};
export const Profile = Template.bind({});
Profile.args = {
src: "/lauraAvatar.svg",
size: "small",
alt: "user profile",
};
Profile.parameters = {
docs: {
description: {
story: `This is a story`,
},
},
};
如您所见,这种编写文档的方式有点繁琐。
MDX
使用 MDX 编写故事解决了之前的问题。它允许任何熟悉简单 Markdown.md格式的人编写文档。主要优势在于,现在非技术团队成员也可以参与组件库的文档编写。
设计师现在可以分享他们的设计令牌并编写文档,帮助开发者理解设计模式背后的原理。这里有一个来自 Storybook MDX 发布文章的非常棒的例子。Philip Siekmann 创建了一个出色的插件,可以根据样式表和资源文件生成设计令牌文档,演示文件就是使用 MDX 编写的。
假设Avatar我们想在故事中添加文档,以确保组件遵循最佳实践。MDX 让这一切变得非常简单。
import { Meta, Story, Canvas, ArgsTable } from "@storybook/addon-docs/blocks";
import Avatar from "../components/Avatar";
<Meta
title="Components/Avatar"
component={Avatar}
argTypes={{
src: {
name: "source",
control: { type: "text" },
},
size: {
name: "size",
defaultValue: "medium",
control: {
type: "select",
options: ["small", "medium", "large"],
},
},
}}
/>
# Avatar
An `Avatar` is a visual representation of a user or entity.
The `small` size should only be used inside a `navbar`.
We should always make sure that we provide an alternative text for screen readers. Therefore, always ensure that the `alt` tag is being used in the component.
<Canvas>
<Story
name="Default"
args={{
src: "/defaultAvatar.svg",
size: "medium",
alt: "default"
}}>
{(args) => <Avatar {...args} />}
</Story>
</Canvas>;
<ArgsTable of={Avatar} />
它将ArgsTable在文档块中渲染一个包含所有可用元素的表格args。请注意,即使我们使用了 Controls 插件,在使用 MDX 时,我们也无法在文档块中动态地与组件的参数进行交互,但我们可以使用组件argsType内部的方法来自定义此表格<Meta />。
如您所见,MDX 结构与 CSF 非常相似,因为在后台,Storybook 会将 MDX 文件编译成 CSF。
源动态代码片段
如果你decorators在组件中使用它,你可能会在源代码片段中遇到类似这样的内容:
// CSF
<div
style={{
margin: "2em",
}}
>
<No Display Name />
</div>
// MDX
<MDXCreateElement
mdxType="div"
originalType="div"
style={{
margin: "2em",
}}
>
<No Display Name />
</MDXCreateElement>
装饰器为组件提供额外的“外部”信息,例如用于样式设置的额外标记、侧载数据或包含所需的上下文ThemeProvider。我们可以全局使用它们,也可以在每个组件内部单独渲染装饰器。当在组件内部局部使用这些装饰器时,会导致源代码片段出现一些问题。
下一版本计划修复此问题,但目前,您可以通过修改源代码来code解决这个问题parameters.docs.source.type。
export default {
title: "Components/Avatar",
component: Avatar,
parameters: {
docs: {
source: {
type: "code",
},
},
},
};
我已经为 Storybook 创建了一个 PR来尝试解决这个问题,所以非常希望得到大家的反馈!
结论
CSF 和 MDX 都是构建组件库的绝佳方式。选择其中一种主要取决于您的团队结构或您计划如何使用组件库。
我最近在推特上发起了一项关于编写用户故事的首选方法的投票,近 70% 的人(约 80 票)选择了使用 CSF,这可以理解,因为它仍然是标准且推荐的方法。但是,在某些情况下,当 CSF 对非技术用户来说有些复杂,或者我们的组件需要精确且结构良好的文档时,MDX 仍然是一种非常便捷的用户故事编写方式。
我认为你不应该在 CSF 和 MDX 之间做出选择,它们都是编写故事的好方法,而且两者互补使用效果最佳。
文章来源:https://dev.to/lauracarballo/storybook-for-everyone-csf-vs-mdx-88b