将Storybook与React和Redux一起使用

来自菜鸟教程
跳转至:导航、​搜索

Storybook 允许您独立开发 UI 组件,可以提高组件的重用性、可测试性和开发速度。

除了这些开发人员体验 (DX) 优势外,Storybook 还可以很好地与传统组件开发配合使用,并且不需要更改核心功能即可显示、测试和记录您的组件库。

它还可以通过“插件”和“装饰器”提供进一步的扩展。 使用 Addons,将 Storybook 与您的开发流程相结合的可能性被解锁。 然后装饰器添加您的组件平稳运行所需的自定义。

React 故事书

使用 Storybook 启动和运行 React 相对简单。 官方文档中提供了完整的设置说明。 这个过程会提供一个.storybook/目录和一个config.js文件。 一旦这些部分到位,为 React 组件创建故事就很简单了。

//in Button.story.js

import React from 'react'
import { storiesOf } from '@storybook/react'

import Button from './Button'

storiesOf('Button', module)
  .add('default', () => (
    <Button>Submit</Button>
  ))
//in .storybook/config.js

import { configure } from '@storybook/react'

configure(function() {
  require('../src/components/Button.story')
}, module)
$ npm run storybook

运行启动 Storybook 的命令将在故事面板中显示新故事。

提供者组件

因为 Storybook 是孤立地展示我们的组件,所以在重新创建我们的应用程序可能提供的高级功能时可能会出现问题,通常在 Root.jsApp.js 中找到。 在 Redux 中,这将是一个 Provider,但这也适用于来自 react-router 的路由器,以及来自 react-i18next 等库的任何 Provider 或来自 material-ui 等库的 ThemeProviders。

为了在不修改组件代码的情况下连接它,我们将使用 Storybook 提供的帮助器来创建装饰器。 Decorators 允许将组件包装在通用代码中,避免冗余和不一致。

在深入研究装饰器之前,我们将从所有高级 Provider 中创建一个 Provider 组件。 这可以像导出 Redux Provider 一样简单,但它也可以包括路由器、国际化设置等。

//in Provider.js

import React from 'react'
import { Router } from 'react-router'
import { Provider } from 'react-redux'

const ProviderWrapper = ({ children, store }) => (
  <Provider store={store}>
    <Router>
      { children }
    </Router>
  </Provider>
)

export default ProviderWrapper

将 Provider 从 Root 中分离出来,它可以很容易地导入到任何组件故事中。

除了导入 Provider 之外,我们还需要导入和传递 Redux 存储的实例。

//in Button.story.js

...

import { storiesOf } from '@storybook/react'

import Provider from '../Provider.js'
import configureStore from '../configuedStore.js'

const store = configureStore()

...

storiesOf('Button', module)

这为故事提供了对 store.dispatch 的轻松访问,因此可以调用 Redux 操作来模拟我们组件中的状态更改。

装饰器

Storybook Decorators 允许将故事包装在组件可能需要正确执行的公共代码中。 一个常见的例子是在页面中将组件居中的包装器,但我们将使用装饰器将 Provider 组件注入到我们的故事中,就像 a HOC

一个简单的实现如下。

//in Button.story.js

// ...

import Provider from '../Provider.js'
import configureStore from '../configuedStore.js'

const store = configureStore()

const withProvider = (story) => (
  <Provider store={store}>
    { story() }
  </Provider>
)

storiesOf('Button', module)
  .addDecorator(withProvider)
  .add('default', () => (
    <Button>Submit</Button>
  ))

这个 withProvider 装饰器可以变成一个实用函数,用于其他故事。 为方便起见,这些装饰器通常保存在一个新的 .storybook/decorators.js 文件中,以便可以在整个代码库中导入它们。

全球装饰器

作为额外的帮助,Storybook 提供了一种轻松将装饰器应用于全球所有故事的方法。 这可以通过在 .storybook/config.js 中调用 addDecorator 来完成

//in .storybook/config.js

import { configure, addDecorator } from '@storybook/react'
import { withProvider } from './decorators'

// ...

addDecorator(withProvider)

开发工具

Redux 开发工具

Redux DevTools 是使用 Redux 开发时的“启动”,为状态更改、动作回放等提供视觉效果。

安装【X17X】浏览器扩展【X42X】后,需要在商店中进行一些代码配置。

//in configureStore.js

import { createStore, applyMiddleware } from 'redux'
import { composeWithDevTools } from 'redux-devtools-extension'

const store = createStore(reducer, composeWithDevTools(
  applyMiddleware(...middleware),
))

过去,Redux DevTools 在使用 Storybook 时遇到了麻烦,因为它将所有组件隔离到一个 iframe 中。 这阻止了扩展程序监听我们故事中的 Redux 操作。

即使这不再是一个大问题,也有一些插件可以提供帮助。

插件

Storybook 原生插件 为 Storybook 添加进一步的开发功能。 这些可以是社区编写的扩展或自定义您的开发流程。 作为配置的一部分,插件注册在一个新的 .storybook/addons.js 文件中。

故事书插件redux-listener

在更直接地修复问题之前,这个 Redux DevTools 监听器插件重新建立了组件与浏览器扩展的连接。 它还为 Storybook 本身添加了一个简单的面板。

配置看起来像这样:

//in .storybook/addons.js

import 'storybook-addon-redux-listener/register'
//in configureStore.js

import createStorybookListener from 'storybook-addon-redux-listener'

...

const middlewares = []

// OPTIONAL: attach when Storybook is active
if (process.env.NODE_ENV === 'storybook') {
  const reduxListener = createStorybookListener()
  middlewares.push(reduxListener)
}

const createStoreWithMiddleware = (reducers) => {
  return createStore(reducers, applyMiddleware(...middlewares))
}

const configureStore = () => createStoreWithMiddleware(reducers)

export default configureStore

插件还原

这个插件添加了多个自定义面板,这些面板重新创建了 DevTools 中的一些功能。

//in .storybook/addons.js

import addons from '@storybook/addons'
import registerRedux from 'addon-redux/register'

registerRedux(addons)
//in configureStore.js

import { createStore, compose } from 'redux'
import reducer from './your/reducer'
import withReduxEnhancer from 'addon-redux/enhancer'

const configureStore = () => createStore(reducer, withReduxEnhancer)

export default configureStore

也可以看看