利用StoryBook开发UI组件管理

前言

最近掐指一算发现本月还有篇技术博文没写~,虽然随便拿一篇日常积累的文章,或者把最近重构的一些点拿出来讲都可以糊弄过去,但是我决定还是搞一点事情。。。

近期就有一个需求是这样的,我手里进行的一个重构项目里,有一些组件我想抽离,给未来其它项目使用,然后我还需要开发两个前端项目,他们有一些共同的组件需求。纯静态页面是不适合的,因为我现在技术栈上了React + Typescipt。我想做到即插即用。顺便把props,state这些东西定义好,以后改一改就能上项目。原本是立了个flag准备自己搞一点东西出来,但是在微信群里,有人扔了一个链接出来storybook,

粗略一看好像就说我需要的。因此今天目标就是捣鼓一份开发环境。

确定需求

Storybook是UI组件的开发环境。它允许您浏览组件库,查看每个组件的不同状态,以及交互式开发和测试组件。

但是官方github的介绍非常贫瘠,因此建议大家看Introduction to Storybook 来了解更多。

以及guide

我们明确一下我们的需求:

支持载入ant-design等UI库

支持Typescript

支持redux

支持参数调试

正式开始

根据思路先创建一个支持ts的react项目

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

然后更新依赖包

yarn upgrade

然后按照 storybook

npm i -g @storybook/cli

cd my-app

getstorybook

之后直接运行yarn run storybook就可以看到界面

然而事实并没有那么简单。因为支持ts的是项目本身,而storybook是独立出来的。因此你需要按照配置进行各种修改。

首先在.storybook目录下建立webpack.config.js

里面加载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;

};

然后对package.json进行改造

{

  "name": "my-app",

  "version": "0.1.0",

  "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"

  }

}

之后最重要的一点是将根目录下的stories目录移到src目录之下.

里面写入一个index.tsx

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', () => );

storiesOf('Button', module)

  .add('with text', () => Hello Button)

  .add('with some emoji', () => 😀 😎 👍 💯);

之后再运行yarn run storybook就可以实现支持ts语法了。

之后我们需要考虑我们的ui组件该如何组织,通过大量翻看gitHub上的源码,大体上两种方式。 一种是在同名组件下直接添加.stories.ts的文件

./Button.jsx

./Button.stories.ts

一种是stories目录下建立index.ts,引用其他组件内容。

我们采取后一种,这是为了方便管理。而且直接在我们现有代码基础上就可以进行。

我们考虑做个demo,现在react + redux 的demo都是用todolist来完成。但是我们这里直接代入一个成熟的redux方案。

首先我们看src目录下现在的结构:

.

├── App.css

├── App.test.tsx

├── App.tsx

├── index.css

├── index.tsx

├── logo.svg

├── registerServiceWorker.ts

├── stories

│  └── index.jsx

├── webpack.config.1.js

└── webpack.config.js

很明显 典型create-app的结构。 然后我们直接看加入redux之后的项目结构:

── src

│  ├── actions

│  │  └── index.ts

│  ├── components

│  ├── constants

│  │  └── index.ts

│  ├── containers

│  │  └── App

│  ├── index.tsx

│  ├── logo.svg

│  ├── reducers

│  │  ├── index.ts

│  │  └── info.ts

│  ├── registerServiceWorker.ts

│  ├── store

│  │  └── index.ts

│  ├── stories

│  │  └── index.jsx

│  ├── typing.d.ts

│  ├── webpack.config.1.js

│  └── webpack.config.js

这里还需要注意的是你需要对tsconfig做一些整个,而且为了支持Less,我对webpack也做了一些修改。

之后我们写一段简单的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: '你好, 临水照影'

    }

  }

  return (dispatch: any) => {

    dispatch(saveList(info))

    return info

  }

}

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;

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 {

  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 (

     

       

{this.state.infoList}

        Click Me

       


    );

  }

}

const mapStateToProps = (state: any) => ({

  info: state.info.info,

})

let AppWrapper = App

AppWrapper = connect(mapStateToProps)(App);

export default AppWrapper;

然后来看下效果图:

可以看到数据已经传递成功。

接下来就是写stories,因为我们用了redux 所以我们需要用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 => {story()})

.add('empty App', () => );

storiesOf('Button', module)

  .add('with text', () => Hello Button)

  .add('with some emoji', () => 😀 😎 👍 💯);

最后一步就是加入参数调试,这里我们需要加在一些addon 来增强体验。

我们可以在More addons这里看到addons列表。根据需求来增加。然后到对应的git网站上去看用法加入即可。

首先加入addon-notes,它用来写组件描述,而且经过测试,它是可以加入Html代码,因此可以先自己定义统一格式,然后加入内容。

还可以自定义一些信息,比如使用参数,暴露出来的接口等等。加载Info Addon 就可以实现。

接下来的核心就是增加参数调试功能

这里给段示例代码:

storiesOf('AppWrapper', module)

.addDecorator(withKnobs)

.addDecorator(story => {story()})

.add('knobs App', () =>)

.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 (

   

      I like:

    {passions.map((p, i) =>
  • {p}
  • )}

     

My favorite number is {favoriteNumber}.

     

My most comfortable room temperature is {comfortTemp} degrees Fahrenheit.


  );

});

需要在开发的时候把动态传递的参数给设定好。这样才能即时显示。效果图如下:

然后调用addon options

在config.js里加入

setOptions({

  downPanelInRight: true,

})

把横轴的显示板变成竖着的。

还有非常多有意思的addons,比如Info的提升版本readme.以及一键换背景的backgrounds。还有现成的Material-UI。还有直接显示你Jsx源码的Storybook-addon-jsx.以及控制版本显示的storybook-addon-versions,让你直接对比多个版本的区别。一键生成所有截图的Storybook Chrome Screenshot Addon。这些社区的addons都非常实用。感兴趣可以自己增加。

结尾

最后我们已经完美完成了之前的需求,还有了一些意外的惊喜。

根据我这份环境配置,可以自行进行扩展,虽然我现在基于React开发。但是StoryBook也是支持Vue的。

最后照惯例放出该工程的github地址

参考资料

前端组件化开发方案及其在React Native中的运用

react-template

playbook_ts_sample

d3-storybook-test

react-storybook-demo

Introduction to Storybook

StoryBook meets redux

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,440评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,814评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,427评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,710评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,625评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,014评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,511评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,162评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,311评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,262评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,278评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,989评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,583评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,664评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,904评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,274评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,856评论 2 339

推荐阅读更多精彩内容

  • core package 概要:Core是所有其他包的基础包.它提供了大部分功能包括metadata,templa...
    LOVE小狼阅读 2,548评论 0 3
  • 前言 本文 有配套视频,可以酌情观看。 文中内容因各人理解不同,可能会有所偏差,欢迎朋友们联系我讨论。 文中所有内...
    珍此良辰阅读 11,884评论 23 111
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,561评论 18 139
  • 都说青春是跌跌撞撞的旅行,有着后知后觉的美丽,当时我们并没有感觉这样的时光有多么的了不起,所以在分别时才会说,后会...
    晓村阅读 212评论 0 0
  • 函数 全局变量 获取全局变量python获取全局变量直接获取 修改全局变量python不允许直接修改全局变量如果要...
    AndroidCat阅读 237评论 0 0