手把手教你快速搭建專屬的storybook

kenzung發表於2019-01-05

手把手教你快速搭建專屬的storybook

什麼是Storybook

Storybook是一個輔助UI控制元件開發的工具。通過story建立獨立的控制元件,讓每個控制元件開發都有一個獨立的開發除錯環境。 Storybook的執行不依賴於專案,開發人員不用擔心由於開發環境、依賴問題導致不能開發控制元件。

Storybook支援的框架覆蓋主流的框架(React、Vue、Angular)。 由於使用React作為技術棧,本文將介紹使用react的專案如何配置Storybook環境。

安裝

  1. 全域性安裝Storybook
npm i -g storybook
複製程式碼
  1. 執行以下命令安裝@storybook/react
npm i --save-dev @storybook/react
複製程式碼
  1. 在package.json檔案中
{
  "scripts": {
    "storybook": "start-storybook -p 9001 -c .storybook"
  }
}
複製程式碼
  1. 在工程根目錄建立.storybook目錄

  2. .storybook目錄下建立config.js檔案

import { configure } from '@storybook/react';
import 'index.scss';

function loadStories() {
  require('./stories/userStory');
}

configure(loadStories, module);
複製程式碼
  1. 建立story
    雖然官方推薦在專案根目錄下建立stories目錄,但我比較喜歡在.storybook目錄下建立一個stories目錄。然後根據不同的業務模組建立不同的stories目錄。 比如有個user模組,那麼我會建立一個stories/userStory目錄。
// stories/userStory/index.jsx
import React from 'react';
import { storiesOf } from '@storybook/react';
import BasicInfo from 'pages/clientDetail/components/BasicInfo';

storiesOf('使用者資訊', module)
  .add('基礎資訊', () => <BasicInfo />);
複製程式碼

至此,根據Storybook React Guide,我們配置了一個簡單的storybook環境。 其實這個環境已經可以用了,當然,如果還需要一些額外的功能,比如支援lessscss等,就需要自定義webpack配置。

storybook 配置

1. 自定義webpack配置

storybook基礎webpack配置只包含以下幾項:

  • babel
    • ES2016+ Support
    • .babelrc support
  • Webpack
    • CSS Support
    • Image and Static File Support
    • JSON Loader

有時候預設的webpack配置不能滿足我們的專案,因此需要對webpack配置進行擴充套件。

通常我使用的是Full Control Mode對webpack配置進行修改。首先在.storybook目錄下增加webpack.config.js檔案

const path = require('path');

module.exports = (storybookBaseConfig, configType) => {
  // 現在應該很多專案會使用`less`或者`scss`等css預處理技術。
  // 這裡使用了postcss-loader進行處理
  storybookBaseConfig.module.rules.push({
    test: /\.s?css$/,
    use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
    include: path.resolve(__dirname, '../'),
  });

  return storybookBaseConfig;
};

複製程式碼

注意 1:預設配置失效

如果使用自定義的配置,預設配置就會失效,如果沒有重新配置file-loader,storybook執行起來時候假如有控制元件引用了圖片等檔案會報錯。

  // 預設配置會失效,處理檔案需要配置相應的file-loader
  storybookBaseConfig.module.rules.push({
    test: /\.(gif|png|jpe?g|eot|woff|ttf|pdf)$/,
    loader: 'file-loader',
  });
複製程式碼

注意 2:保留原配置上修改

使用Full Control Mode模式雖然可以最大限度修改storybook的webpack配置,但是以下配置修改時需要注意在原配置上進行擴充套件。

  • entry
  • output
  • first loader in the module.loaders (Babel loader for JS)
  • all existing plugins

比如需要新增一個loader,需要像面那樣push一個loader到module.rules陣列中。

storybookBaseConfig.module.rules.push({
  test: /\.s?css$/,
  use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
  include: path.resolve(__dirname, '../'),
});
複製程式碼

2. storybook + Redux

其實元件可以分為2種,具體可以參照這篇文章

  • 展示類,不涉及邏輯只關注樣式展示(Presentational)
  • 邏輯類,關注邏輯而不涉及樣式展示(Container)

展示類

展示類的控制元件使用Storybook很簡單,根據展示類的控制元件傳入props即可。

邏輯類

在我的專案中通常為react-redux connect後的類。類中操作(如網路操作)都是通過redux進行的。 為了讓這個類能正常測試執行,需要進行以下操作。(例子均為userStory

  1. 在story檔案中引入react-redux的providerstore
import { provider } from 'react-redux';
import store from 'store';
複製程式碼
  1. 擴充套件story
    在storiesOf中增加裝飾器,裝飾器作用就是把story包裹起來。 因此我們可以利用裝飾器的特點把store引入到控制元件中。
// stories/userStory/index.jsx
import React from 'react';
import { provider } from 'react-redux'
import { storiesOf } from '@storybook/react';
// 引入store
import store from 'store';
import BasicInfo from 'pages/clientDetail/components/BasicInfo';

storiesOf('使用者資訊', module)
  .addDecorator(storyFn => <Provider store={store}>{storyFn()}</Provider>)
  .add('基礎資訊', () => <BasicInfo />);
複製程式碼
  1. mock資料
    團隊搭建的yapi平臺負責mock資料生成。

  2. whistle
    由於mock資料和storybook不在同一個域,js呼叫mock資料會跨域,需要做請求轉發代理。我們團隊使用的是whistle。whistle是個好東西?,牆裂推薦!!!

    以下是whistle配置的轉發規則。

//yourproject.com resCors://*
//localhost:8888 resCors://enable
//yourproject.com http://127.0.0.1:8888/  weinre://
# 9001是storybook的埠
# https://myapi.xxx.com 是yapi所在域名。
^localhost:9001/cgi-bin/** //myapi.xxx.com/mock/3876/cgi-bin/$1
複製程式碼

addons

什麼是addons,其實可以理解擴充套件storybook功能的外掛。

我使用到的addons有

  • addon-actions 用於展示事件處理函式接收到的資料
  • addon-console console輸出(log、error、warning)
  • addon-info 這個最有用了,如果控制元件填寫了proptype,直接就能顯示到storybook中。
  • addon-viewport 其實就是chrome的device toolbar功能。

注意
addon-actions和addon-viewport都需要在addons.js中註冊才能使用。

實踐

實踐程式碼

這個就是我的.storybook目錄結構。

.
├── README.md
├── addons.js
├── config.js
├── stories
│   └── userStory
│       └── index.js
└── webpack.config.js
複製程式碼
  • config.js
import { configure } from '@storybook/react';
import { setConsoleOptions } from '@storybook/addon-console';
import 'index.scss';

setConsoleOptions({
  panelExclude: [],
});

function loadStories() {
  require('./stories/userStory');
}

configure(loadStories, module);
複製程式碼
  • webpack.config.js
const path = require('path');

module.exports = (storybookBaseConfig, configType) => {
  storybookBaseConfig.module.rules.push({
    test: /\.s?css$/,
    use: ['style-loader', { loader: 'css-loader', options: { importLoaders: 1 } }, 'postcss-loader'],
    include: path.resolve(__dirname, '../'),
  });

  storybookBaseConfig.module.rules.push({
    test: /\.(gif|png|jpe?g|eot|woff|ttf|pdf)$/,
    loader: 'file-loader',
  });

  // 設定別名
  storybookBaseConfig.resolve.alias = {
    antd: path.resolve(__dirname, '..', `node_modules/antd/dist/antd.min.js`),
    antdcss: path.resolve(__dirname, '..', 'node_modules/antd/dist/antd.min.css'),
    antdzhCN: path.resolve(__dirname, '..', 'node_modules/antd/lib/locale-provider/zh_CN.js'),
  };

  // 增加src為絕對路徑
  storybookBaseConfig.resolve.modules.push(path.resolve(__dirname, '..', 'src'));

  // 使用source-map
  storybookBaseConfig.devtool = 'source-map';

  storybookBaseConfig.mode = 'development';

  return storybookBaseConfig;
};
複製程式碼
  • addons.js
import '@storybook/addon-actions/register';
import '@storybook/addon-viewport/register';
複製程式碼

action和viewport addon均需要在addons.js中註冊才能正常使用。

  • userStory
import React from 'react';
import { Provider } from 'react-redux';
import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { withConsole } from '@storybook/addon-console';
import BasicInfo from 'pages/clientDetail/components/BasicInfo';
import KeyActionItem from 'pages/clientDetail/components/KeyAction/KeyActionItem';
import { withInfo } from '@storybook/addon-info';
import store from 'pages/clientDetail/store';
import zhCN from 'antdzhCN';
import { LocaleProvider } from 'antd';
import 'antdcss';

// redux結合
storiesOf('使用者資訊', module)
  .addDecorator(withInfo)
  .addDecorator((storyFn, context) => withConsole()(storyFn)(context))
  .addDecorator(storyFn => <Provider store={store}>{storyFn()}</Provider>)
  .addDecorator(storyFn => <LocaleProvider locale={zhCN}>{storyFn()}</LocaleProvider>)
  .add('基礎資訊', () => <BasicInfo />, {
    info: {
      text: `
        使用者基礎資訊展示,可進行上下翻頁。
      `,
    },
  });

storiesOf('行為軌跡item', module)
  .addDecorator(withInfo)
  .addDecorator((storyFn, context) => withConsole()(storyFn)(context))
  .add('行為軌跡item--課程顧問', () => (
    <div style={{ display: 'flex', justifyContent: 'center', paddingTop: '30px' }}>
      <KeyActionItem
        time="2018-12-12 12:12:12"
        user="testUser"
        role="課程顧問"
        type="saler"
        data={[{ title: '備註', content: '備註測試'}]}
        id={1}
        onClickDelete={action('onClickDelete')}
        canDelete
      />
    </div>
  ))
  .add('行為軌跡item--客戶', () => (
    <div style={{ display: 'flex', justifyContent: 'center', paddingTop: '30px' }}>
      <KeyActionItem
        time="2018-12-12 12:12:12"
        user="testUser"
        role="家長"
        type="client"
        data={[{ title: '購買記錄', content: '測試購買記錄'}]}
      />
    </div>
  ))
  .add('行為軌跡item--admin', () => (
    <div style={{ display: 'flex', justifyContent: 'center', paddingTop: '30px' }}>
      <KeyActionItem
        time="2018-12-12 12:12:12"
        user="admin"
        type="admin"
        data={[{ title: '購買記錄', content: '測試購買記錄'}]}
      />
    </div>
  ));
複製程式碼

實踐截圖

手把手教你快速搭建專屬的storybook

手把手教你快速搭建專屬的storybook
addons-info控制元件能把元件的prop資訊展示出來

結語

我在本文中介紹了最基本的storybook使用。

  1. storybook配置
  2. webpack配置
  3. redux引入
  4. addons介紹

文章中介紹的使用方式實在是很簡單,希望能安利更多人使用storybook。更多(高大上)storybook的實踐可以參考這裡 storybook實踐

參考

  1. Storybook官網
  2. StoryBook meets redux

相關文章