一個 react-native Storybook 擴充功能,用於顯示預覽的不同背景

在 Github 上檢視

適用於 React Native 的 Storybook

[!重要]
此 README 適用於 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 中 stories glob 的動態 story 匯入。我們也可以從 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'];

撰寫 Story

在 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 設定檔中設定 Story 檔案的路徑。

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

const main: StorybookConfig = {
  stories: ['../components/**/*.stories.?(ts|tsx|js|jsx)'],
  addons: [],
};

export default main;

裝飾器和參數

對於 Story,您可以在預設匯出或特定的 Story 上新增裝飾器和參數。

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 將為您安裝一些基本的擴充功能,例如 controls 和 actions。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;

在您的 Story 中使用擴充功能

有關每個 OnDevice 擴充功能的詳細資訊,您可以查看 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 設定。這對於同時使用 Storybook 和不使用 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 中產生 .storybook/storybook.requires 檔案,而不是 TypeScript。

configPath

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

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

websockets

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

如果指定,則在啟動時建立 WebSocket 伺服器。這可讓您同步多個裝置,以顯示相同的 Story 和與 UI 中 Story 連接的 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
}

在單元測試中使用 Story

Storybook 提供測試公用程式,可讓您在外部測試環境(例如 Jest)中重複使用您的 Story。這樣,您可以更輕鬆地撰寫單元測試,並重複使用 Storybook 中已完成的設定,但在您的單元測試中。您可以在可攜式 Story 區段中找到更多相關資訊。

貢獻

我們歡迎大家為 Storybook 貢獻!

  • 📥 歡迎提交 Pull request 和 🌟 點讚。
  • 請閱讀我們的貢獻指南以開始使用,或在 Discord 上找到我們並尋找 react-native 頻道。

正在尋找第一個要處理的問題嗎?

  • 當我們認為問題很適合剛接觸程式碼庫或一般 OSS 的人時,我們會使用 Good First Issue 來標記問題。
  • 與我們交談,我們會找到適合您的技能和學習興趣的內容。

範例

以下是一些範例專案,可協助您入門

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