writing-react-native-storybook-stories by storybookjs/react-native
npx skills add https://github.com/storybookjs/react-native --skill writing-react-native-storybook-stories使用 @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广告位招租
在这里展示您的产品或服务
触达数万 AI 开发者,精准高效
satisfies Meta<typeof Component>metaStoryObj<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>
),
};
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: '自定义显示名称',
args: { label: 'Hello' },
};
const meta = {
title: 'NestingExample/Message/Bubble',
component: MyComponent,
} satisfies Meta<typeof MyComponent>;
完整的控件类型参考,请查看 references/controls.md。
常见模式:
const meta = {
component: MyComponent,
argTypes: {
// 选择下拉框
size: {
options: ['small', 'medium', 'large'],
control: { type: 'select' },
},
// 范围滑块
opacity: {
control: { type: 'range', min: 0, max: 1, step: 0.1 },
},
// 颜色选择器
color: { control: { type: 'color' } },
// 条件控件(仅在 `advanced` 参数为 true 时显示)
padding: { control: 'number', if: { arg: 'advanced' } },
},
} satisfies Meta<typeof MyComponent>;
自动检测:TypeScript 属性类型会自动映射到控件(string -> 文本,boolean -> 布尔值,联合类型 -> 选择,number -> 数字)。
parameters: {
// Notes 插件标签页中的 Markdown 文档
notes: `# MyComponent\n用法:\`<MyComponent label="hi" />\``,
// Backgrounds 插件的背景选项
backgrounds: {
default: 'dark',
values: [
{ name: 'light', value: 'white' },
{ name: 'dark', value: '#333' },
],
},
},
| 参数 | 类型 | 描述 |
|---|---|---|
noSafeArea | boolean | 移除顶部安全区域内边距。使用此参数时,组件本身必须处理安全区域,因为 Storybook 将不再提供安全区域内边距。建议使用 useSafeAreaInsets() 而不是 SafeAreaView — 将插图作为 paddingTop/paddingBottom 应用于容器,对于可滚动内容,使用 contentContainerStyle 内边距而不是包装在 SafeAreaView 中。 |
storybookUIVisibility | 'visible' | 'hidden' |
hideFullScreenButton | boolean | 隐藏全屏切换按钮 |
layout | 'padded' | 'centered' |
参数可以在故事、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;
import type { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [
'@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();
});
// 在测试中覆盖参数
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 | 交互式编辑属性 |
| Actions | @storybook/addon-ondevice-actions | 记录组件交互 |
| Backgrounds | @storybook/addon-ondevice-backgrounds | 更改故事背景 |
| Notes | @storybook/addon-ondevice-notes | 添加 Markdown 文档 |
每周安装量
162
仓库
GitHub 星标数
1.3K
首次出现
2026年2月2日
安全审计
安装于
codex144
opencode142
gemini-cli140
github-copilot139
amp133
kimi-cli132
Write stories for React Native components using @storybook/react-native v10 and Component Story Format (CSF).
Minimal story file:
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 colocated with the componentMeta and StoryObj from @storybook/react-nativemeta object with satisfies Meta<typeof Component>StoryObj<typeof meta>args for props, argTypes for control config, parameters for addon configrender for custom render functions, decorators for wrappersexport 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>
),
};
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>;
Or via 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>;
For the full control type reference, see references/controls.md.
Common patterns:
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>;
Auto-detection: TypeScript prop types are automatically mapped to controls (string -> text, boolean -> boolean, union types -> select, number -> 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' },
],
},
},
| Parameter | Type | Description |
|---|---|---|
noSafeArea | boolean | Remove top safe area padding. When using this, the component itself must handle safe areas since Storybook will no longer provide safe area padding. Prefer useSafeAreaInsets() over SafeAreaView — apply insets as paddingTop/paddingBottom on the container, and for scrollable content use contentContainerStyle padding instead of wrapping in SafeAreaView. |
Parameters can be set at story, meta (component), or global (preview.tsx) level.
Wrap stories in providers, layouts, or context:
const meta = {
component: MyComponent,
decorators: [
(Story) => (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
<Story />
</View>
),
],
} satisfies Meta<typeof MyComponent>;
Global decorators go in .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;
import type { StorybookConfig } from '@storybook/react-native';
const main: StorybookConfig = {
stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
addons: [
'@storybook/addon-ondevice-controls',
'@storybook/addon-ondevice-backgrounds',
'@storybook/addon-ondevice-actions',
'@storybook/addon-ondevice-notes',
],
framework: '@storybook/react-native',
};
export default main;
Story globs also support the object form for multi-directory setups:
stories: [
'../components/**/*.stories.?(ts|tsx|js|jsx)',
{ directory: '../other_components', files: '**/*.stories.?(ts|tsx|js|jsx)' },
],
Reuse stories in Jest tests:
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();
});
For single stories use composeStory:
import { composeStory } from '@storybook/react';
import meta, { Primary } from './Button.stories';
const PrimaryStory = composeStory(Primary, meta);
Setup global annotations for tests in a Jest setup file:
// setup-portable-stories.ts
import { setProjectAnnotations } from '@storybook/react';
import * as previewAnnotations from '../.rnstorybook/preview';
setProjectAnnotations(previewAnnotations);
| Addon | Package | Purpose |
|---|---|---|
| Controls | @storybook/addon-ondevice-controls | Edit props interactively |
| Actions | @storybook/addon-ondevice-actions | Log component interactions |
| Backgrounds | @storybook/addon-ondevice-backgrounds | Change story backgrounds |
| Notes | @storybook/addon-ondevice-notes | Add markdown documentation |
Weekly Installs
162
Repository
GitHub Stars
1.3K
First Seen
Feb 2, 2026
Security Audits
Gen Agent Trust HubPassSocketPassSnykPass
Installed on
codex144
opencode142
gemini-cli140
github-copilot139
amp133
kimi-cli132
UI组件模式实战指南:构建可复用React组件库与设计系统
10,700 周安装
云迁移规划指南:策略、最佳实践与Python评估工具,实现平稳云端迁移
131 周安装
Webhook集成指南:实现实时通信与第三方服务集成的最佳实践
131 周安装
AXe iOS 模拟器 UI 自动化 CLI 工具:基于辅助功能 API 的稳定测试
131 周安装
iOS Core Data 完整指南:与 SwiftData 对比、现代堆栈设置与迁移策略
131 周安装
iOS深度链接调试指南:SwiftUI自动化测试与截图解决方案
131 周安装
iOS SwiftUI/UIKit 照片库访问指南:PhotoKit、PhotosPicker、PHPicker 隐私保护开发
131 周安装
storybookUIVisibility | 'visible' | 'hidden' |
hideFullScreenButton | boolean | Hide fullscreen toggle |
layout | 'padded' | 'centered' |