在您的裝置上顯示 Storybook 控制項。

在 Github 上檢視

適用於 React Native 的 Storybook

[!IMPORTANT]
此 readme 適用於 v8,v7 的文件請參閱 v7.6 文件

透過適用於 React Native 的 Storybook,您可以設計和開發個別的 React Native 元件,而無需執行您的應用程式。

如果您要從 7.6 遷移到 8.3,可以在此處找到遷移指南

如需更多關於 storybook 的資訊,請瀏覽:storybook.js.org

[!NOTE]
@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.dev.org.tw/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 會為您安裝一些基本的擴充功能,例如控制項和動作。裝置上的擴充功能是可以與您在手機上看到的裝置 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;

在您的故事中使用擴充功能

有關每個裝置上擴充功能的詳細資訊,您可以查看 readme

隱藏/顯示 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 設定。這對於同時使用 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: process.env.WITH_STORYBOOK,
  // ... other options
});

onDisabledRemoveStorybook

類型:boolean,預設值:false

如果 onDisabledRemoveStorybook 為 trueenabledfalse,則 storybook 套件將會從組建中移除。如果您想從您的生產組建中移除 storybook,這會很有用。

useJs

類型:boolean,預設值:false

以 JavaScript 而非 TypeScript 產生 .storybook/storybook.requires 檔案。

configPath

類型:string,預設值:path.resolve(process.cwd(), './.storybook')

您的 Storybook 設定目錄的位置,其中包括 main.ts 和其他專案相關檔案。

websockets

類型:{ host: string?, port: number? },預設值:undefined

如果指定,則在啟動時建立 WebSocket 伺服器。這可讓您同步多個裝置以顯示相同的故事,以及與 UI 中的故事連線的 arg 值。

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 做出貢獻!

  • 📥 隨時歡迎提取請求和 🌟 星星。
  • 請閱讀我們的貢獻指南以開始使用,或在Discord 上找到我們,並尋找 react-native 頻道。

正在尋找要解決的第一個問題?

  • 當我們認為這些問題很適合程式碼庫或一般 OSS 的新手時,我們會使用 Good First Issue 來標記問題。
  • 與我們交談,我們會找到適合您技能和學習興趣的東西。

範例

以下是一些範例專案,可協助您開始使用

由以下人員製作
  • domyen
    domyen
  • kasperpeulen
    kasperpeulen
  • valentinpalkovic
    valentinpalkovic
  • jreinhold
    jreinhold
  • kylegach
    kylegach
  • ndelangen
    ndelangen
標籤