mcpField
W
writing-react-native-storybook-stories

writing-react-native-storybook-stories

github.com/storybookjs开发1.3k688 次安装4 天前更新
代码仓库

关于 writing-react-native-storybook-stories

这是一项使用 `@storybook/react-native` v10 和 CSF 为 React Native 组件编写 Story 的技能,涵盖故事结构、控件、装饰器与参数,也介绍在 Jest 测试中复用故事。

主要功能

  1. 01CSF 故事文件规范
  2. 02共享 args 与自定义渲染
  3. 03控件与 argTypes 配置
  4. 04参数与装饰器设置
  5. 05在 Jest 中复用故事

适用场景

  1. 01记录组件的不同变体
  2. 02配置交互式属性控件
  3. 03为故事添加 Provider
  4. 04在 Jest 测试中复用故事

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/storybookjs/react-native --skill writing-react-native-storybook-stories

安装后在 Claude Code 中输入/writing-react-native-storybook-stories确认已加载。

附带文件references/controls.md

详细介绍

React Native Storybook 故事

使用 @storybook/react-native v10 和组件故事格式(CSF)为 React Native 组件编写故事。

快速开始

最简故事文件:

import type { Meta, StoryObj } from '@storybook/react-native';
import { MyComponent } from './MyComponent';

const meta = {
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;

export default meta;
type Story = StoryObj<typeof meta>;

export const Basic: Story = {
  args: {
    label: 'Hello',
  },
};

文件约定

  • 名称:ComponentName.stories.tsx,与组件放在同一目录
  • 从 @storybook/react-native 导入 Meta 和 StoryObj
  • 默认导出:meta 对象,并满足 Meta<typeof Component> 类型
  • 命名导出:使用大驼峰命名的故事名称,类型为 StoryObj<typeof meta>
  • 使用 args 指定属性,使用 argTypes 配置控件,使用 parameters 配置插件
  • 使用 render 定义自定义渲染函数,使用 decorators 添加包装器

故事模式

使用共享参数定义多个故事
export const Primary: Story = {
  args: { variant: 'primary', title: 'Click me' },
};

export const Secondary: Story = {
  args: { ...Primary.args, variant: 'secondary' },
};
自定义渲染函数
export const WithScrollView: Story = {
  render: (args) => (
    <ScrollView>
      <MyComponent {...args} />
    </ScrollView>
  ),
};
使用 Hook 进行渲染(必须是命名函数)
export const Interactive: Story = {
  render: function InteractiveRender() {
    const [count, setCount] = useReducer((s) => s + 1, 0);
    return <Counter count={count} onPress={setCount} />;
  },
};
操作(模拟回调)
import { fn } from 'storybook/test';

const meta = {
  component: Button,
  args: { onPress: fn() },
} satisfies Meta<typeof Button>;

也可以通过 argTypes 配置:

argTypes: { onPress: { action: 'pressed' } },
自定义故事名称
export const MyStory: Story = {
  storyName: 'Custom Display Name',
  args: { label: 'Hello' },
};
自定义标题 / 嵌套
const meta = {
  title: 'NestingExample/Message/Bubble',
  component: MyComponent,
} satisfies Meta<typeof MyComponent>;

控件与 ArgTypes

有关控件类型的完整参考,请参阅 references/controls.md。

常见模式:

const meta = {
  component: MyComponent,
  argTypes: {
    // Select dropdown
    size: {
      options: ['small', 'medium', 'large'],
      control: { type: 'select' },
    },
    // Range slider
    opacity: {
      control: { type: 'range', min: 0, max: 1, step: 0.1 },
    },
    // Color picker
    color: { control: { type: 'color' } },
    // Conditional control (shows only when `advanced` arg is true)
    padding: { control: 'number', if: { arg: 'advanced' } },
  },
} satisfies Meta<typeof MyComponent>;

自动检测:TypeScript 属性类型会自动映射到控件(string → 文本,boolean → 布尔值,联合类型 → 选择框,number → 数字)。

参数

插件参数
parameters: {
  // Markdown docs in the Notes addon tab
  notes: `# MyComponent\nUsage: \`<MyComponent label="hi" />\``,
  // Background options for Backgrounds addon
  backgrounds: {
    default: 'dark',
    values: [
      { name: 'light', value: 'white' },
      { name: 'dark', value: '#333' },
    ],
  },
},
RN 专属 UI 参数
参数类型描述
noSafeAreaboolean移除顶部安全区域内边距。使用此参数时,组件本身必须处理安全区域,因为 Storybook 不再提供安全区域内边距。建议使用 useSafeAreaInsets(),而不是 SafeAreaView:将内边距应用为容器的 paddingTop/paddingBottom;对于可滚动内容,请使用 contentContainerStyle 内边距,而不是用 SafeAreaView 包裹。
storybookUIVisibility'visible' | 'hidden'UI 的初始可见性
hideFullScreenButtonboolean隐藏全屏切换按钮
layout'padded' | 'centered' | 'fullscreen'故事容器布局

参数可以在故事、meta(组件)或全局(preview.tsx)级别设置。

装饰器

在提供程序、布局或上下文中包装故事:

const meta = {
  component: MyComponent,
  decorators: [
    (Story) => (
      <View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
        <Story />
      </View>
    ),
  ],
} satisfies Meta<typeof MyComponent>;

全局装饰器放在 .rnstorybook/preview.tsx 中:

import { withBackgrounds } from '@storybook/addon-ondevice-backgrounds';
import type { Preview } from '@storybook/react-native';

const preview: Preview = {
  decorators: [withBackgrounds],
  parameters: {
    actions: { argTypesRegex: '^on[A-Z].*' },
    controls: {
      matchers: {
        color: /(background|color)$/i,
        date: /Date$/,
      },
    },
    backgrounds: {
      default: 'plain',
      values: [
        { name: 'plain', value: 'white' },
        { name: 'dark', value: '#333' },
      ],
    },
  },
};

export default preview;

配置

.rnstorybook/main.ts
import type { StorybookConfig } from '@storybook/react-native';

const main: StorybookConfig = {
  stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
  deviceAddons: [
    '@storybook/addon-ondevice-controls',
    '@storybook/addon-ondevice-backgrounds',
    '@storybook/addon-ondevice-actions',
    '@storybook/addon-ondevice-notes',
  ],
  framework: '@storybook/react-native',
};

export default main;

故事 glob 也支持多目录设置所需的对象形式:

stories: [
  '../components/**/*.stories.?(ts|tsx|js|jsx)',
  { directory: '../other_components', files: '**/*.stories.?(ts|tsx|js|jsx)' },
],

可移植故事(测试)

在 Jest 测试中复用故事:

import { render, screen } from '@testing-library/react-native';
import { composeStories } from '@storybook/react';
import * as stories from './Button.stories';

const { Primary, Secondary } = composeStories(stories);

test('renders primary button', () => {
  render(<Primary />);
  expect(screen.getByText('Click me')).toBeTruthy();
});

// Override args in tests
test('renders with custom props', () => {
  render(<Primary title="Custom" />);
  expect(screen.getByText('Custom')).toBeTruthy();
});

对于单个故事,请使用 composeStory:

import { composeStory } from '@storybook/react';
import meta, { Primary } from './Button.stories';

const PrimaryStory = composeStory(Primary, meta);

在 Jest 设置文件中为测试设置全局注解:

// setup-portable-stories.ts
import { setProjectAnnotations } from '@storybook/react';
import * as previewAnnotations from '../.rnstorybook/preview';
setProjectAnnotations(previewAnnotations);

插件摘要

插件包用途
Controls@storybook/addon-ondevice-controls交互式编辑 props
Actions@storybook/addon-ondevice-actions记录组件交互
Backgrounds@storybook/addon-ondevice-backgrounds更改故事背景
Notes@storybook/addon-ondevice-notes添加 Markdown 文档

评论

支持 Markdown