preface

Recently, I found that there was still a technical blog post that I hadn’t written in this month. Although I could have fooled you with an article I accumulated daily or some points I reconfigured recently, I decided to do something.

One recent requirement was that I was working on a refactoring project that had some components that I wanted to pull out for future projects, and then I had to develop two front-end projects that had some component requirements in common. Purely static pages are not suitable because I am now on the React + Typescipt technology stack. I want to plug and play. I want to define props and state, and then I want to change it so that I can add it to the project. Originally, I set up a flag to create something by myself, but in the wechat group, someone threw a link to a storybook.

At first glance it looks like I need it. So today’s goal is to craft a development environment.

Determine the requirements

Storybook is the development environment for UI components. It allows you to browse component libraries, see the different states of each component, and develop and test components interactively.

However, the official Github Introduction is pretty barren, so I recommend reading Introduction to Storybook for more information.

And guide

Let’s clarify our requirements:

  1. Supports loading of UI libraries such as Ant-Design
  2. Support the Typescript
  3. Support the story
  4. Support parameter debugging

The official start of the

Create a React project that supports TS

create-react-app my-app --scripts-version=react-scripts-ts

Then update the dependency package

yarn upgrade

Then follow the storybook


npm i -g @storybook/cli
cd my-app
getstorybook

Copy the code

Then run Yarn Run Storybook directly to see the screen

But the truth is not that simple. Because ts is supported by the project itself, storybook stands alone. So you need to make all kinds of changes according to the configuration.

Start by creating webpack.config.js in the.storybook directory

It loads typescript-loader


// load the default config generator.
const genDefaultConfig = require('@storybook/react/dist/server/config/defaults/webpack.config.js');
module.exports = (baseConfig, env) => {
    const config = genDefaultConfig(baseConfig, env);
    // Extend it as you need.
    // For example, add typescript loader:
    config.module.rules.push({
        test: /\.(ts|tsx)$/,
        loader: require.resolve('awesome-typescript-loader')}); config.resolve.extensions.push('.ts'.'.tsx');
    return config;
};

Copy the code

Then modify package.json


{ 
  "name": "my-app"."version": "0.1.0 from"."private": true."dependencies": {
    "react": "^ 16.0.0"."react-dom": "^ 16.0.0"."react-scripts-ts": "2.7.0"
  },
  "scripts": {
    "start": "react-scripts-ts start"."build": "react-scripts-ts build"."test": "react-scripts-ts test --env=jsdom"."eject": "react-scripts-ts eject"."storybook": "start-storybook -p 6006"."build-storybook": "build-storybook"
  },
  "devDependencies": {
    "@storybook/addon-actions": "^ 3.2.12." "."@storybook/addon-links": "^ 3.2.12." "."@storybook/react": "^ 3.2.12." "."@types/jest": "^ 21.1.2"."@types/node": "^ 8.0.39"."@types/react": "^ 16.0.12"."@types/react-dom": "^ 16.0.1"."@types/storybook__react": "^ 3.0.5"."awesome-typescript-loader": "^ 3.2.3"}}Copy the code

The most important next step is to move the stories directory from the root directory under SRC.

Write an index. TSX inside


import React from 'react';

import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';

import { Button, Welcome } from '@storybook/react/demo';

storiesOf('Welcome', module).add('to Storybook', () => <Welcome showApp={linkTo('Button')} / >); storiesOf('Button', module)
  .add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
  .add('with some emoji', () => <Button onClick={action('clicked')} > 😀 😎 👍 💯 < / Button >);Copy the code

Then run YARN Run Storybook to support TS syntax.

Then we need to think about how our UI components should be organized by looking through the gitHub source code in two general ways. One is to add a.stories.ts file directly under the component with the same name


./Button.jsx
./Button.stories.ts

Copy the code

One is to create index.ts in the Stories directory and reference other component content.

We adopt the latter for the convenience of management. And we can do it directly from our existing code base.

We’re thinking about doing a demo. Now react + Redux demos are all done using Todolist. But let’s just plug in a full-blown REdux scheme.

First let’s look at the current structure of the SRC directory:

. ├ ─ ─ App. CSS ├ ─ ─ App. Test. The TSX ├ ─ ─ App. The TSX ├ ─ ─ index. The CSS ├ ─ ─ index. The TSX ├ ─ ─ logo. The SVG ├ ─ ─ registerServiceWorker. Ts ├ ─ ─ │ ├─ ├─ ├─ ├.txt TXT TXT TXT TXT TXT TXT TXT TXT TXTCopy the code

The typical create-App structure is obvious. Then let’s go straight to the project structure with redux:

─ ─ the SRC │ ├ ─ ─ the actions │ │ └ ─ ─ but ts │ ├ ─ ─ components │ ├ ─ ─ constants │ │ └ ─ ─ index. The ts │ ├ ─ ─ containers │ │ └ ─ ─ App │ ├ ─ ─ index. The TSX │ ├ ─ ─ logo. The SVG │ ├ ─ ─ reducers │ │ ├ ─ ─ but ts │ │ └ ─ ─ the info. The ts │ ├ ─ ─ registerServiceWorker. Ts │ ├ ─ ─ store │ │ │ └ ─ ─ but ts ├ ─ ─ stories │ │ └ ─ ─ index. The JSX │ ├ ─ ─ typing, which s │ ├ ─ ─ webpack. Config. 1. Js │ └ ─ ─ webpack. Config. JsCopy the code

The other thing to note here is that you need to do some overall work on tsconfig, and I made some changes to WebPack to support Less.

After that, we wrote a simple action to reducer

action:


import { INFO_LIST } from '.. /constants/index'
const saveList = (data: Object) => ({
  type: INFO_LIST,
  data: data,
})

export function infoListRemote () {
  const info = {
    data: {
      item: 'Hello LinShuiZhaoYing',
      cnItem: 'Hello, by the water.'}}return (dispatch: any) => {
    dispatch(saveList(info))
    return info
  }
}

Copy the code

reducer:


import { INFO_LIST } from '.. /constants';

const initialState = {
   info:' '
}

const info = (state = initialState, action: any) => {
  // console.log(action)
  switch (action.type) {
    case INFO_LIST:
      return {
        ...state,
        info:action.data.data
      }
    default:
      return state
  }
}

export default info;

Copy the code

App:

index.tsx:


import * as React from 'react';
import { Button, Icon } from 'antd';
import { connect } from 'react-redux';
import { infoListRemote } from '.. /.. /actions/index';
import './index.css';

class App extends React.Component<any, any> {
  constructor (props: any) {
    super(props)
    this.state = {
      infoList: ' ',}}componentWillMount() {}componentDidMount() {
    // console.log(this.props)
  }
  componentWillReceiveProps(nextProps: any) {
    // console.log(nextProps)
    if (nextProps.info) {
      this.setState({
        infoList: nextProps.info.item
      })
    }
  }

  getInfo = () => {
    const { dispatch } = this.props;
    dispatch(infoListRemote())
  }

  render() {
    return (
      <div className="App">
        <div className="test"> {this.state.infoList} </div>
        <Button type="danger" onClick={this.getInfo}> Click Me</Button>
        <Icon type="play-circle-o" />
      </div>
    );
  }
}
const mapStateToProps = (state: any) => ({
  info: state.info.info,
})
let AppWrapper = App
AppWrapper = connect(mapStateToProps)(App);

export default AppWrapper;

Copy the code

Then take a look at the effect:

You can see that the data has been successfully delivered.

The next step is to write stories, since we are using Redux we need to wrap our component with addDecorator


import React from 'react';

import { storiesOf } from '@storybook/react';
import { action } from '@storybook/addon-actions';
import { linkTo } from '@storybook/addon-links';
import { Provider } from 'react-redux';
import { Button, Welcome } from '@storybook/react/demo';
import { createBrowserHistory } from 'history';
import configureStore from '.. /store';

import  AppWrapper  from '.. /containers/App'

const store = configureStore(createBrowserHistory);

storiesOf('AppWrapper', module)
.addDecorator(story => <Provider store={store}>{story()}</Provider>)
.add('empty App', () => <AppWrapper />);

storiesOf('Button', module)
  .add('with text', () => <Button onClick={action('clicked')}>Hello Button</Button>)
  .add('with some emoji', () => <Button onClick={action('clicked')} > 😀 😎 👍 💯 < / Button >);Copy the code

The last step is to add parameter debugging, here we need to add some addon to enhance the experience.

We can see the list of addons here in More Addons. Increase according to demand. Then go to the corresponding Git website to see how to add.

Addon-notes is first used to write component descriptions, and it has been tested to be capable of adding Html code, so you can define your own uniform format before adding content.

You can also customize some information, such as the parameters used, the interfaces exposed, and so on. This can be done by loading Info Addon.

The next core is to add parameter debugging

Here is some sample code:


storiesOf('AppWrapper', module)
.addDecorator(withKnobs)
.addDecorator(story => <Provider store={store}>{story()}</Provider>)
.add('knobs App', () =><AppWrapper text={text('Label'.'Hello World')}></AppWrapper>)
.add('with all knobs', () => {
  const name = text('Name'.'Tom Cary');
  const dob = date('DOB', new Date('January 20 1887'));

  const bold = boolean('Bold'.false);
  const selectedColor = color('Color'.'black');
  const favoriteNumber = number('Favorite Number', 42);
  const comfortTemp = number('Comfort Temp', 72, { range: true, min: 60, max: 90, step: 1 });

  const passions = array('Passions'['Fishing'.'Skiing']);

  const customStyle = object('Style', {
    fontFamily: 'Arial', padding: 20, }); const style = { ... customStyle, fontWeight: bold ? 800 : 400, favoriteNumber, color: selectedColor, };return( <div style={style}> I like: <ul>{passions.map((p, i) => <li key={i}>{p}</li>)}</ul> <p>My favorite number is {favoriteNumber}.</p> <p>My most comfortable room temperature  is {comfortTemp} degrees Fahrenheit.</p> </div> ); });Copy the code

The parameters that need to be passed dynamically need to be set up at development time. So it can be displayed in real time. The renderings are as follows:

Then call Addon Options

Add it to config.js


setOptions({
  downPanelInRight: true,})Copy the code

Change the display board on the horizontal axis to vertical.

There are also lots of interesting Addons, such as readme, an improved version of Info. We can also change the background by clicking the button. There is also a ready-made Material-UI. There is also a storybook-addon-jsx that directly displays your Jsx source code. And the storybook-addon-versions that control the version display, allowing you to compare the differences between versions directly. One click to generate all screenshots in Storybook Chrome Screenshot Addon. These community addons are very practical. Interest can grow on its own.

At the end

In the end, we have completed the previous requirements perfectly, and there are some unexpected surprises.

Based on my environment configuration, I can extend it myself, although I’m currently developing based on React. But StoryBook also supports Vue.

Finally, the project’s Github address was released as usual

The resources

Front-end componentization development scheme and its application in React Native

react-template

playbook_ts_sample

d3-storybook-test

react-storybook-demo

Introduction to Storybook

StoryBook meets redux