在您的设备上显示 Storybook 故事控件。

在 Github 上查看

适用于 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 或更高版本。

picture of storybook

目录

入门

新项目

有一些项目样板文件,其中包含已配置好的 @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 中的故事 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 进行渲染的插件。

目前可用的插件有

安装您要使用的每个插件,并将其添加到 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
});

选项

启用

类型:布尔值,默认值: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

类型:布尔值,默认值:false

如果 onDisabledRemoveStorybook 为 trueenabledfalse,则 Storybook 包将从构建中删除。如果您希望从生产构建中删除 Storybook,这将非常有用。

useJs

类型:布尔值,默认值:false

以 JavaScript 而不是 TypeScript 生成 .storybook/storybook.requires 文件。

configPath

类型:字符串,默认值:path.resolve(process.cwd(), './.storybook')

Storybook 配置目录的位置,其中包含 main.ts 和其他与项目相关的文件。

网络套接字

类型:{ host: string?, port: number? },默认值:undefined

如果指定,则在启动时创建 WebSocket 服务器。这允许您同步多个设备以显示相同的故事和参数值,这些值连接到 UI 中的故事。

websockets.host

类型:字符串,默认值:'localhost'

如果指定,则运行 WebSocket 的主机。

websockets.port

类型:数字,默认值: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 贡献力量!

  • 📥 拉取请求和 🌟 星标始终受欢迎。
  • 阅读我们的贡献指南以开始使用,或者在Discord上找到我们,并查找 React Native 频道。

正在寻找第一个要解决的问题?

  • 当我们认为某些问题非常适合刚接触代码库或开源软件的人时,我们会为这些问题添加良好的首个问题标签。
  • 与我们联系,我们会找到适合您的技能和学习兴趣的内容。

示例

以下是一些示例项目,可帮助您入门

由以下人员制作
  • domyen
    domyen
  • kasperpeulen
    kasperpeulen
  • valentinpalkovic
    valentinpalkovic
  • jreinhold
    jreinhold
  • kylegach
    kylegach
  • ndelangen
    ndelangen
标签