React Native 的 Storybook
[!重要]
本自述文件适用于 v8,有关 v7 文档,请参阅 v7.6 文档。
使用 React Native 的 Storybook,您可以设计和开发单个 React Native 组件,而无需运行您的应用程序。
如果您正在从 7.6 迁移到 8.3,您可以在 此处 找到迁移指南。
有关 storybook 的更多信息,请访问:storybook.js.org
[!注意]
@storybook/react-native
至少需要 8.3.1,如果您安装其他 storybook 核心包,它们应该为^8.3.1
或更新版本。
目录
- 🚀 入门
- 📒 编写故事
- 🔌 插件
- 📱 隐藏/显示 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
剩下要做的就是将 Storybook 的 UI 返回到您的应用程序入口点(例如 App.tsx
)中,如下所示
export { default } from './.storybook';
然后,将您的 metro 配置用 withStorybook 函数包装起来,如 下方 所示
如果您希望能够轻松地在 storybook 和您的应用程序之间切换,请查看此 博客文章
如果您想自己添加所有内容,请查看此处的手动指南 此处。
其他步骤:更新您的 metro 配置
我们需要 unstable_allowRequireContext 变换器选项来启用基于 main.ts
中的故事 glob 的动态故事导入。我们还可以从 metro 配置中调用 storybook 生成函数,以便在 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 将为您安装一些基本插件,例如控件和操作。Ondevice 插件是可以在您在手机上看到的设备 UI 中渲染的插件。
目前可用的插件是
@storybook/addon-ondevice-controls
: 实时调整您的组件属性@storybook/addon-ondevice-actions
: 模拟带有将在操作选项卡中记录信息的 action 的 onPress 调用@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 插件的详细信息,您可以查看自述文件
隐藏/显示 Storybook
React Native 上的 Storybook 是一个正常的 React Native 组件,可以在您的 RN 应用程序中的任何位置根据您自己的逻辑使用或隐藏。
您还可以创建一个专门用于 storybook 的应用程序,该应用程序也可以用作您的视觉组件的包。有些人选择使用 react native 开发者菜单中的自定义选项来切换 storybook 组件。
withStorybook 包装器
withStorybook
是一个包装器函数,用于扩展您的 Metro 配置 以用于 Storybook。它接受您现有的 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 的项目设置很有用,并且需要有条件地应用选项。在此示例中,它是使用环境变量有条件地进行的
// 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,这很有用。
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 服务器。这使您可以同步多个设备以显示相同的 story 和 arg 值连接到 UI 中的 story。
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
shouldPersistSelection: Boolean (true)
-- Stores last selected story in your devices storage.
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 中完成的设置,但在您的单元测试中。您可以在 可移植故事部分 中找到有关此方面的更多信息。
贡献
我们欢迎您为 Storybook 贡献力量!
正在寻找第一个要解决的问题吗?
- 当我们认为它们非常适合刚接触代码库或 OSS 的人时,我们会将问题标记为 Good First Issue。
- 与我们交谈,我们会找到适合您的技能和学习兴趣的东西。
示例
以下是一些示例项目,可以帮助您入门
- 由 @axeldelafosse 创建的单仓库设置 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 并将其添加到列表中!