writing-react-native-storybook-stories
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseReact Native Storybook Stories
React Native Storybook 故事编写
Write stories for React Native components using v10 and Component Story Format (CSF).
@storybook/react-native使用 v10和Component Story Format (CSF)为React Native组件编写故事。
@storybook/react-nativeQuick Start
快速入门
Minimal story file:
tsx
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',
},
};最简故事文件:
tsx
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',
},
};File Conventions
文件约定
- Name: colocated with the component
ComponentName.stories.tsx - Import and
MetafromStoryObj@storybook/react-native - Default export: object with
metasatisfies Meta<typeof Component> - Named exports: UpperCamelCase story names, typed as
StoryObj<typeof meta> - Use for props,
argsfor control config,argTypesfor addon configparameters - Use for custom render functions,
renderfor wrappersdecorators
- 命名:,与组件放在同一目录下
ComponentName.stories.tsx - 从导入
@storybook/react-native和MetaStoryObj - 默认导出:对象,需满足
meta类型Meta<typeof Component> - 命名导出:故事名称采用大驼峰式,类型为
StoryObj<typeof meta> - 使用传递属性,
args配置控件,argTypes配置插件parameters - 使用定义自定义渲染函数,
render定义包装器decorators
Story Patterns
故事编写模式
Multiple stories with shared args
共享参数的多个故事
tsx
export const Primary: Story = {
args: { variant: 'primary', title: 'Click me' },
};
export const Secondary: Story = {
args: { ...Primary.args, variant: 'secondary' },
};tsx
export const Primary: Story = {
args: { variant: 'primary', title: 'Click me' },
};
export const Secondary: Story = {
args: { ...Primary.args, variant: 'secondary' },
};Custom render function
自定义渲染函数
tsx
export const WithScrollView: Story = {
render: (args) => (
<ScrollView>
<MyComponent {...args} />
</ScrollView>
),
};tsx
export const WithScrollView: Story = {
render: (args) => (
<ScrollView>
<MyComponent {...args} />
</ScrollView>
),
};Render with hooks (must be a named function)
结合Hooks渲染(必须是命名函数)
tsx
export const Interactive: Story = {
render: function InteractiveRender() {
const [count, setCount] = useReducer((s) => s + 1, 0);
return <Counter count={count} onPress={setCount} />;
},
};tsx
export const Interactive: Story = {
render: function InteractiveRender() {
const [count, setCount] = useReducer((s) => s + 1, 0);
return <Counter count={count} onPress={setCount} />;
},
};Actions (mock callbacks)
动作(模拟回调)
tsx
import { fn } from 'storybook/test';
const meta = {
component: Button,
args: { onPress: fn() },
} satisfies Meta<typeof Button>;Or via argTypes:
tsx
argTypes: { onPress: { action: 'pressed' } },tsx
import { fn } from 'storybook/test';
const meta = {
component: Button,
args: { onPress: fn() },
} satisfies Meta<typeof Button>;或者通过argTypes:
tsx
argTypes: { onPress: { action: 'pressed' } },Custom story name
自定义故事名称
tsx
export const MyStory: Story = {
storyName: 'Custom Display Name',
args: { label: 'Hello' },
};tsx
export const MyStory: Story = {
storyName: 'Custom Display Name',
args: { label: 'Hello' },
};Custom title / nesting
自定义标题/嵌套结构
tsx
const meta = {
title: 'NestingExample/Message/Bubble',
component: MyComponent,
} satisfies Meta<typeof MyComponent>;tsx
const meta = {
title: 'NestingExample/Message/Bubble',
component: MyComponent,
} satisfies Meta<typeof MyComponent>;Controls & ArgTypes
控件与ArgTypes
For the full control type reference, see references/controls.md.
Common patterns:
tsx
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 ( -> text, -> boolean, union types -> select, -> number).
stringbooleannumber完整的控件类型参考请见references/controls.md。
常见配置模式:
tsx
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属性类型会自动映射为控件(→文本框、→布尔开关、联合类型→下拉选择器、→数字输入框)。
stringbooleannumberParameters
参数
Addon parameters
插件参数
tsx
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' },
],
},
},tsx
parameters: {
// 在Notes插件标签页中显示Markdown文档
notes: `# MyComponent\nUsage: \`<MyComponent label="hi" />\``,
// Backgrounds插件的背景选项
backgrounds: {
default: 'dark',
values: [
{ name: 'light', value: 'white' },
{ name: 'dark', value: '#333' },
],
},
},RN-specific UI parameters
React Native专属UI参数
| Parameter | Type | Description |
|---|---|---|
| | 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 |
| | Initial UI visibility |
| | Hide fullscreen toggle |
| | Story container layout |
Parameters can be set at story, meta (component), or global (preview.tsx) level.
| 参数名称 | 类型 | 说明 |
|---|---|---|
| | 移除顶部安全区域内边距。使用此参数时,组件本身必须处理安全区域,因为Storybook将不再提供安全区域内边距。优先使用 |
| | 初始UI可见性 |
| | 隐藏全屏切换按钮 |
| | 故事容器布局 |
参数可在故事、元数据(组件)或全局(preview.tsx)级别设置。
Decorators
装饰器
Wrap stories in providers, layouts, or context:
tsx
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.tsxtsx
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;用提供者、布局或上下文包裹故事:
tsx
const meta = {
component: MyComponent,
decorators: [
(Story) => (
<View style={{ alignItems: 'center', justifyContent: 'center', flex: 1 }}>
<Story />
</View>
),
],
} satisfies Meta<typeof MyComponent>;全局装饰器需放在中:
.rnstorybook/preview.tsxtsx
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;Configuration
配置
.rnstorybook/main.ts
.rnstorybook/main.ts
ts
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:
ts
stories: [
'../components/**/*.stories.?(ts|tsx|js|jsx)',
{ directory: '../other_components', files: '**/*.stories.?(ts|tsx|js|jsx)' },
],ts
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;故事路径通配符也支持对象形式,适用于多目录场景:
ts
stories: [
'../components/**/*.stories.?(ts|tsx|js|jsx)',
{ directory: '../other_components', files: '**/*.stories.?(ts|tsx|js|jsx)' },
],Portable Stories (Testing)
可移植故事(测试用)
Reuse stories in Jest tests:
tsx
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 :
composeStorytsx
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:
ts
// setup-portable-stories.ts
import { setProjectAnnotations } from '@storybook/react';
import * as previewAnnotations from '../.rnstorybook/preview';
setProjectAnnotations(previewAnnotations);在Jest测试中复用故事:
tsx
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();
});单个故事可使用:
composeStorytsx
import { composeStory } from '@storybook/react';
import meta, { Primary } from './Button.stories';
const PrimaryStory = composeStory(Primary, meta);在Jest配置文件中设置全局注解:
ts
// setup-portable-stories.ts
import { setProjectAnnotations } from '@storybook/react';
import * as previewAnnotations from '../.rnstorybook/preview';
setProjectAnnotations(previewAnnotations);Addons Summary
插件汇总
| Addon | Package | Purpose |
|---|---|---|
| Controls | | Edit props interactively |
| Actions | | Log component interactions |
| Backgrounds | | Change story backgrounds |
| Notes | | Add markdown documentation |
| 插件名称 | 包名 | 用途 |
|---|---|---|
| Controls | | 交互式编辑组件属性 |
| Actions | | 记录组件交互行为 |
| Backgrounds | | 切换故事背景 |
| Notes | | 添加Markdown文档说明 |