适用于 React Native 的 Storybook
[!重要]
本 README 适用于 v9 beta 版,v8 文档请参阅 v8.6 文档。
使用适用于 React Native 的 Storybook,您无需运行应用即可设计和开发单个 React Native 组件。
如果您从 8 迁移到 9,可以在此处找到迁移指南
有关 storybook 的更多信息,请访问:storybook.js.org
[!注意]
请确保您的 storybook 依赖项与主版本保持一致,否则可能会出现异常行为。
目录
- 🚀 开始入门
- 📒 编写故事
- 🔌 插件
- 📱 隐藏/显示 Storybook
- ⚙️ withStorybook 包装器
- 🔧 getStorybookUI 选项
- 🧪 在单元测试中使用故事
- 🤝 贡献
- ✨ 示例
开始入门
新项目
这里有一些项目模板,其中 @storybook/react-native
和 @storybook/addon-react-native-web
都已配置好,并提供了一个简单的示例。
对于 expo,您可以使用以下命令使用此模板
# With NPM
npx create-expo-app --template expo-template-storybook AwesomeStorybook
对于 react native cli,您可以使用此模板
npx react-native init MyApp --template react-native-template-storybook
现有项目
运行 init 命令来设置您的项目,安装所有依赖项和配置文件
npx storybook@latest init
剩下的唯一事情是在您的应用入口点(例如 App.tsx
)中返回 Storybook 的 UI,像这样
export { default } from './.storybook';
然后使用 withStorybook 函数包装您的 metro 配置,如下文所示
如果您想在 storybook 和您的应用之间轻松切换,请参阅这篇博文
如果您想自己添加所有内容,请查阅此处的手动指南。
额外步骤:更新您的 metro 配置
我们需要 unstable_allowRequireContext 转换器选项,以根据 main.ts 中的故事通配符启用动态故事导入。我们还可以在 metro 配置中调用 storybook generate 函数,以便在 metro 运行时自动生成 storybook.requires.ts 文件。
Expo
如果您还没有 metro 配置文件,请先创建一个。
npx expo customize metro.config.js
然后使用 withStorybook 函数包装您的配置,如下所示。
// metro.config.js
const path = require('path');
const { getDefaultConfig } = require('expo/metro-config');
const withStorybook = require('@storybook/react-native/metro/withStorybook');
/** @type {import('expo/metro-config').MetroConfig} */
const config = getDefaultConfig(__dirname);
module.exports = withStorybook(config, {
// Set to false to remove storybook specific options
// you can also use a env variable to set this
enabled: true,
// Path to your storybook config
configPath: path.resolve(__dirname, './.storybook'),
// Optional websockets configuration
// Starts a websocket server on the specified port and host on metro start
// websockets: {
// port: 7007,
// host: 'localhost',
// },
});
React Native
const { getDefaultConfig, mergeConfig } = require('@react-native/metro-config');
const path = require('path');
const withStorybook = require('@storybook/react-native/metro/withStorybook');
const defaultConfig = getDefaultConfig(__dirname);
/**
* Metro configuration
* https://reactnative.net.cn/docs/metro
*
* @type {import('metro-config').MetroConfig}
*/
const config = {};
// set your own config here 👆
const finalConfig = mergeConfig(defaultConfig, config);
module.exports = withStorybook(finalConfig, {
// Set to false to remove storybook specific options
// you can also use a env variable to set this
enabled: true,
// Path to your storybook config
configPath: path.resolve(__dirname, './.storybook'),
// Optional websockets configuration
// Starts a websocket server on the specified port and host on metro start
// websockets: {
// port: 7007,
// host: 'localhost',
// },
});
Reanimated 设置
请确保您的项目中安装了 react-native-reanimated
,并且在您的 babel 配置中设置了相应的插件。
// babel.config.js
plugins: ['react-native-reanimated/plugin'],
编写故事
在 storybook 中,我们使用一种名为 CSF 的语法,它看起来像这样
import type { Meta, StoryObj } from '@storybook/react';
import { MyButton } from './Button';
const meta = {
component: MyButton,
} satisfies Meta<typeof MyButton>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Basic: Story = {
args: {
text: 'Hello World',
color: 'purple',
},
};
您应该在 .storybook 文件夹中的 main.ts
配置文件中配置故事文件的路径。
// .storybook/main.ts
import { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [],
};
export default main;
装饰器和参数
对于故事,您可以在默认导出或特定故事上添加装饰器和参数。
import type { Meta } from '@storybook/react';
import { Button } from './Button';
const meta = {
title: 'Button',
component: Button,
decorators: [
(Story) => (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
<Story />
</View>
),
],
parameters: {
backgrounds: {
values: [
{ name: 'red', value: '#f00' },
{ name: 'green', value: '#0f0' },
{ name: 'blue', value: '#00f' },
],
},
},
} satisfies Meta<typeof Button>;
export default meta;
对于全局装饰器和参数,您可以将它们添加到 .storybook 文件夹内的 preview.tsx
中。
// .storybook/preview.tsx
import type { Preview } from '@storybook/react';
import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';
const preview: Preview = {
decorators: [
withBackgrounds,
(Story) => (
<View style={{ flex: 1, color: 'blue' }}>
<Story />
</View>
),
],
parameters: {
backgrounds: {
default: 'plain',
values: [
{ name: 'plain', value: 'white' },
{ name: 'warm', value: 'hotpink' },
{ name: 'cool', value: 'deepskyblue' },
],
},
},
};
export default preview;
插件
CLI 将为您安装一些基本插件,例如 controls 和 actions。Ondevice 插件是指可以在您手机上看到的设备 UI 中渲染的插件。
目前可用的插件有
@storybook/addon-ondevice-controls
:实时调整您的组件属性@storybook/addon-ondevice-actions
:使用 actions 模拟 onPress 调用,这些 actions 会在 actions 标签页中记录信息@storybook/addon-ondevice-notes
:在您的故事中添加一些 Markdown 来帮助记录其用法@storybook/addon-ondevice-backgrounds
:更改 storybook 的背景,以对比您的组件在不同背景下的外观
安装您想使用的每个插件,并将其添加到 main.ts
插件列表中,如下所示
// .storybook/main.ts
import { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
// ... rest of config
addons: [
'@storybook/addon-ondevice-notes',
'@storybook/addon-ondevice-controls',
'@storybook/addon-ondevice-backgrounds',
'@storybook/addon-ondevice-actions',
],
};
export default main;
在您的故事中使用插件
每个 ondevice 插件的详细信息请参阅其 README
隐藏/显示 Storybook
React Native 上的 Storybook 是一个普通的 React Native 组件,您可以根据自己的逻辑在 RN 应用程序的任何地方使用或隐藏它。
您还可以专门为 storybook 创建一个单独的应用,它也可以作为您的视觉组件的包。有些人选择通过在 react native 开发者菜单中使用自定义选项来切换 storybook 组件。
withStorybook 包装器
withStorybook
是一个包装函数,用于为 Storybook 扩展您的 Metro 配置。它接受您现有的 Metro 配置以及一个选项对象,用于配置 Storybook 如何启动和运行。
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const withStorybook = require('@storybook/react-native/metro/withStorybook');
const defaultConfig = getDefaultConfig(__dirname);
module.exports = withStorybook(defaultConfig, {
enabled: true,
// See API section below for available options
});
选项
enabled
类型:boolean
,默认值:true
确定指定的选项是否应用于 Metro 配置。这对于同时使用或不使用 Storybook 的项目设置非常有用,需要有条件地应用选项。在此示例中,通过环境变量使其具有条件性
// metro.config.js
const { getDefaultConfig } = require('expo/metro-config');
const withStorybook = require('@storybook/react-native/metro/withStorybook');
const defaultConfig = getDefaultConfig(__dirname);
module.exports = withStorybook(defaultConfig, {
enabled: process.env.WITH_STORYBOOK,
// ... other options
});
onDisabledRemoveStorybook
类型:boolean
,默认值:false
如果 onDisabledRemoveStorybook true
且 enabled
为 false
,则 storybook 包将从构建中移除。如果您想从生产构建中移除 storybook,这非常有用。
useJs
类型:boolean
,默认值:false
生成 JavaScript 格式的 .storybook/storybook.requires
文件,而不是 TypeScript 格式。
configPath
类型:string
,默认值:path.resolve(process.cwd(), './.storybook')
您的 Storybook 配置文件目录的位置,其中包括 main.ts
和其他项目相关文件。
websockets
类型:{ host: string?, port: number? }
,默认值:undefined
如果指定,则在启动时创建一个 WebSocket 服务器。这允许您同步多个设备,以在 UI 中显示与故事连接的相同故事和 arg 值。
websockets.host
类型:string
,默认值:'localhost'
如果指定,运行 WebSocket 的主机。
websockets.port
类型:number
,默认值:7007
如果指定,运行 WebSocket 的端口。
getStorybookUI 选项
您可以在 storybook 入口点将这些参数传递给 getStorybookUI 调用
{
initialSelection?: string | Object (undefined)
-- initialize storybook with a specific story. eg: `mybutton--largebutton` or `{ kind: 'MyButton', name: 'LargeButton' }`
storage?: Object (undefined)
-- {getItem: (key: string) => Promise<string | null>;setItem: (key: string, value: string) => Promise<void>;}
-- Custom storage to be used instead of AsyncStorage
onDeviceUI?: boolean;
-- show the ondevice ui
enableWebsockets?: boolean;
-- enable websockets for the storybook ui
query?: string;
-- query params for the websocket connection
host?: string;
-- host for the websocket connection
port?: number;
-- port for the websocket connection
secured?: boolean;
-- use secured websockets
shouldPersistSelection?: boolean;
-- store the last selected story in the device's storage
theme: Partial<Theme>;
-- theme for the storybook ui
}
在单元测试中使用故事
Storybook 提供了测试实用程序,允许您在外部测试环境(如 Jest)中重用您的故事。这样,您可以更轻松地编写单元测试,并重用 Storybook 中已完成的设置,但在您的单元测试中。您可以在portable stories 部分找到更多相关信息。
贡献
我们欢迎对 Storybook 的贡献!
寻找第一个要解决的问题?
- 当我们认为它们非常适合刚接触代码库或一般 OSS 的人时,我们会为问题标记 Good First Issue。
- 与我们交流,我们会找到适合您的技能和学习兴趣的事情。
示例
这里有一些示例项目可以帮助您开始
- @axeldelafosse 创建的 mono repo 设置 https://github.com/axeldelafosse/storybook-rnw-monorepo
- Expo 设置 https://github.com/dannyhw/expo-storybook-starter
- React Native CLI 设置 https://github.com/dannyhw/react-native-storybook-starter
- 为 RN CLI 项目在原生文件中添加单独的入口点和开发者菜单项: https://github.com/zubko/react-native-storybook-with-dev-menu
- 想展示您自己的项目?提交一个 PR 并将其添加到列表中!