前言
最近掐指一算發(fā)現(xiàn)本月還有篇技術(shù)博文沒寫~,雖然隨便拿一篇日常積累的文章非迹,或者把最近重構(gòu)的一些點(diǎn)拿出來講都可以糊弄過去蹦魔,但是我決定還是搞一點(diǎn)事情。娄帖。。
近期就有一個需求是這樣的昙楚,我手里進(jìn)行的一個重構(gòu)項(xiàng)目里近速,有一些組件我想抽離,給未來其它項(xiàng)目使用堪旧,然后我還需要開發(fā)兩個前端項(xiàng)目削葱,他們有一些共同的組件需求。純靜態(tài)頁面是不適合的淳梦,因?yàn)槲椰F(xiàn)在技術(shù)棧上了React + Typescipt析砸。我想做到即插即用。順便把props,state這些東西定義好爆袍,以后改一改就能上項(xiàng)目首繁。原本是立了個flag準(zhǔn)備自己搞一點(diǎn)東西出來,但是在微信群里螃宙,有人扔了一個鏈接出來storybook,
粗略一看好像就說我需要的蛮瞄。因此今天目標(biāo)就是搗鼓一份開發(fā)環(huán)境。
確定需求
Storybook是UI組件的開發(fā)環(huán)境谆扎。它允許您瀏覽組件庫挂捅,查看每個組件的不同狀態(tài),以及交互式開發(fā)和測試組件堂湖。
但是官方github的介紹非常貧瘠闲先,因此建議大家看Introduction to Storybook?來了解更多状土。
以及guide
我們明確一下我們的需求:
支持載入ant-design等UI庫
支持Typescript
支持redux
支持參數(shù)調(diào)試
正式開始
根據(jù)思路先創(chuàng)建一個支持ts的react項(xiàng)目
create-react-app my-app --scripts-version=react-scripts-ts
然后更新依賴包
yarn upgrade
然后按照 storybook
npm i -g @storybook/cli
cd my-app
getstorybook
之后直接運(yùn)行yarn run storybook就可以看到界面
然而事實(shí)并沒有那么簡單。因?yàn)橹С謙s的是項(xiàng)目本身伺糠,而storybook是獨(dú)立出來的蒙谓。因此你需要按照配置進(jìn)行各種修改。
首先在.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進(jìn)行改造
{
? "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"
? }
}
之后最重要的一點(diǎn)是將根目錄下的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', () => ?? ?? ?? ??);
之后再運(yùn)行yarn run storybook就可以實(shí)現(xiàn)支持ts語法了训桶。
之后我們需要考慮我們的ui組件該如何組織累驮,通過大量翻看gitHub上的源碼,大體上兩種方式舵揭。 一種是在同名組件下直接添加.stories.ts的文件
./Button.jsx
./Button.stories.ts
一種是stories目錄下建立index.ts,引用其他組件內(nèi)容谤专。
我們采取后一種,這是為了方便管理午绳。而且直接在我們現(xiàn)有代碼基礎(chǔ)上就可以進(jìn)行置侍。
我們考慮做個demo,現(xiàn)在react?+?redux?的demo都是用todolist來完成拦焚。但是我們這里直接代入一個成熟的redux方案蜡坊。
首先我們看src目錄下現(xiàn)在的結(jié)構(gòu):
.
├── 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的結(jié)構(gòu)。 然后我們直接看加入redux之后的項(xiàng)目結(jié)構(gòu):
── 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 (
? ? ?
? ? ? ?
? ? ? ? Click Me
? ? ? ?
? ? );
? }
}
const mapStateToProps = (state: any) => ({
? info: state.info.info,
})
let AppWrapper = App
AppWrapper = connect(mapStateToProps)(App);
export default AppWrapper;
然后來看下效果圖:
可以看到數(shù)據(jù)已經(jīng)傳遞成功。
接下來就是寫stories,因?yàn)槲覀冇昧藃edux?所以我們需要用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', () => ?? ?? ?? ??);
最后一步就是加入?yún)?shù)調(diào)試螟够,這里我們需要加在一些addon?來增強(qiáng)體驗(yàn)灾梦。
我們可以在More addons這里看到addons列表峡钓。根據(jù)需求來增加妓笙。然后到對應(yīng)的git網(wǎng)站上去看用法加入即可。
首先加入addon-notes能岩,它用來寫組件描述寞宫,而且經(jīng)過測試,它是可以加入Html代碼拉鹃,因此可以先自己定義統(tǒng)一格式辈赋,然后加入內(nèi)容。
還可以自定義一些信息膏燕,比如使用參數(shù)钥屈,暴露出來的接口等等。加載Info Addon?就可以實(shí)現(xiàn)坝辫。
接下來的核心就是增加參數(shù)調(diào)試功能
這里給段示例代碼:
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.
? );
});
需要在開發(fā)的時候把動態(tài)傳遞的參數(shù)給設(shè)定好篷就。這樣才能即時顯示。效果圖如下:
然后調(diào)用addon options
在config.js里加入
setOptions({
? downPanelInRight: true,
})
把橫軸的顯示板變成豎著的近忙。
還有非常多有意思的addons竭业,比如Info的提升版本readme.以及一鍵換背景的backgrounds智润。還有現(xiàn)成的Material-UI。還有直接顯示你Jsx源碼的Storybook-addon-jsx.以及控制版本顯示的storybook-addon-versions未辆,讓你直接對比多個版本的區(qū)別窟绷。一鍵生成所有截圖的Storybook Chrome Screenshot Addon。這些社區(qū)的addons都非常實(shí)用咐柜。感興趣可以自己增加兼蜈。
結(jié)尾
最后我們已經(jīng)完美完成了之前的需求,還有了一些意外的驚喜拙友。
根據(jù)我這份環(huán)境配置饭尝,可以自行進(jìn)行擴(kuò)展,雖然我現(xiàn)在基于React開發(fā)献宫。但是StoryBook也是支持Vue的钥平。
最后照慣例放出該工程的github地址
參考資料