Redux开发初探

Jul 31, 2016


概述

服务端的研发工程师或多或少也会参与一些前端的开发工作,有时候是因为前端人力的匮乏(以前实习的服务端组就没有前端,后台管理的网页都需要自己写),有时是因为自己想开发一些组内的小工具。因此,掌握一套前端开发技术,能够快速的构建网站也算是一个必备的技能。
在上述的背景下,由于组内目前采用了Redux来进行开发,我也趁机初步实践了一下,这里来分享一下我的实践结果。
注意:本文不涉及基本概念和语法的介绍,相关内容可参见:

用到的主要技术和工具

开发工具
  • WebStorm
  • Intellij Idea
  • Chrome&React插件
相关技术和组件
  • React
  • Redux
  • Material-UI
  • Node.js
  • SpringBoot

React的思想

React是由Facebook推出的JavaScript库,用于简化和规范前端的开发,同时优化页面的性能。作为一个写Java的后端开发,以前对JavaScript的印象就是灵活而混乱,100人的JavaScript代码100种风格,有的工程师好歹会设计一下,进行一些模块化的提取和复用,而有的工程师甚至把JS都往一个html活着vm文件里写,简直要命。而React的出现,以及ES6的应用,能够有效提高前端代码的可读性,同时提高其渲染性能(关于性能方面,因为还怎么接触,暂时不做讨论)。

归纳一下,我理解的React的思想可以分为这几部分:

  • 组件化,其实ReactES6的结合,写起来真的挺像Java,在开发过程中能够很好的进行组件的分离和复用。
  • 状态(state)参数(props)来代表组件的内容,每一个组件都有其独立的状态,该状态受组件内部行为的影响,同时受到外部参数的影响而改变。
  • 单向数据流,相比于双向绑定,实际是一个简化的思想,虽然会带来更多的代码,但流程更加清晰,问题更好追踪。
  • 页面的渲染由React控制,React会根据组件state的改变情况来决定是否进行重新渲染。

所以,React相当于给了我们一个最佳实践的模版,开发者只需要关心行为数据,完全从页面的渲染部分解放了出来。

Redux的思想

理解了React的思想,那么就我的理解而言,Redux实际上是一个管理action,stateprops的工具,React让开发者需要关注用户的行为和数据,而Redux为我们提供了一个管理这两者的最佳实践模版。
Redux共由三个主要组件:

  • Action 定义动作的类型,和动作中携带的参数
  • Reducer 进行动作的处理,和状态的改变
  • Store 相当于一个小的数据库,用来存储所有组件的状态

Redux的规定,将用户操作的行为定义为Action,这些Action都需要由Reducer去处理。如果需要获取远端的数据,可以利用fetchmiddleware来实现。

代码实现

1.定义显示模块

import React, { Component } from 'react';
import { connect } from 'react-redux';
import {Table, TableBody, TableHeader, TableHeaderColumn, TableRow, TableRowColumn} from 'material-ui/Table';
import FlatButton from 'material-ui/FlatButton';
import MenuItem from 'material-ui/MenuItem';
import RefreshIndicator from 'material-ui/RefreshIndicator';

const commentStyle = {
    width: 300
};

const filedStyle = {
    width: 200
};

const loadingStyle = {
    display: 'inline-block',
    position: 'relative'
};

class App extends Component {

    constructor(props) {
        super(props);
        this.state = {
            showCheckboxes: false,
            open: false,
            info: '暂无内容可以显示',
            scrollable: true,
            detectHeight: true,
        };
    }

    hanldeClose() {
        this.setState({
            open: false
        });
    }

    render() {

        return (
            <div>
                <div>
                    <MenuItem>
                        表名:{this.props.params.tableName}
                    </MenuItem>
                </div>
                <div>
                    <Table>
                        <TableHeader
                            displaySelectAll={this.state.showCheckboxes}
                            adjustForCheckbox={this.state.showCheckboxes}>
                            <TableRow>
                                <TableHeaderColumn style={filedStyle}>字段名</TableHeaderColumn>
                                <TableHeaderColumn style={commentStyle}>字段描述</TableHeaderColumn>
                                <TableHeaderColumn>字段类型</TableHeaderColumn>
                                <TableHeaderColumn>是否可为空</TableHeaderColumn>
                                <TableHeaderColumn>键类型</TableHeaderColumn>
                                <TableHeaderColumn>默认值</TableHeaderColumn>
                            </TableRow>
                        </TableHeader>
                        <TableBody displayRowCheckbox={this.state.showCheckboxes}>
                            {this.props.tableItems.map((tableItem)=>(
                                <TableRow key={tableItem[0]}>
                                    <TableRowColumn style={filedStyle}>
                                        {tableItem[0]}
                                    </TableRowColumn>
                                    <TableRowColumn style={commentStyle}>
                                        {tableItem[1]}
                                    </TableRowColumn>
                                    <TableRowColumn>
                                        {tableItem[2]}
                                    </TableRowColumn>
                                    <TableRowColumn>
                                        {tableItem[3]}
                                    </TableRowColumn>
                                    <TableRowColumn>
                                        {tableItem[4]}
                                    </TableRowColumn>
                                    <TableRowColumn >
                                        {tableItem[5]}
                                    </TableRowColumn>
                                </TableRow>)
                            )}
                        </TableBody>
                    </Table>
                    <RefreshIndicator
                        size={50}
                        left={512}
                        top={0}
                        loadingColor={"#FF9800"}
                        status={this.props.loading}
                        style={loadingStyle}
                    />
                </div>
            </div >);
    }
}

/**
 * 将状态的改变绑定到props
 */
const mapStateToProps = (state)=> {
    return {
        params: state.requestReducer.tableParams,
        tableItems: state.requestReducer.tableItems,
        loading: state.requestReducer.loading
    }
};

export default connect(mapStateToProps)(App);

该模块为一个table,其参数主要为tableItems,通过mapStateToProps方法,该参数将会随着状态中的state.requestReducer.tableItems而改变。

2.定义Action

import fetch from 'isomorphic-fetch';


/**
 * 请求表数据中
 */
export const REQUESTING = "REQUESTING";

/**
 * 获取到表的描述信息
 */
export const RECEIVE_TABLE_DESCRIPTIONS = "RECEIVE_TABLE_DESCRIPTIONS";

/**
 * 获取表的描述信息
 * @param params 请求参数
 * @param tableItems 表的描述项
 */
export function receiveTableDescriptions(tableParams, tableItems) {
	return {
    	type: RECEIVE_TABLE_DESCRIPTIONS,
    	tableParams,
    	tableItems
	}
}

/**
 * 请求参数
 */
export function requesting(loading) {
	return {
    	type: REQUESTING,
    	loading
	}
}

/**
 * 根据数据库名和表名获取表的描述
 */
export function fetchTableDescriptions(tableParams) {
	return function (dispatch) {
    	dispatch(requesting({loading: 'loading'}));
    	return fetch(`http://yourServiceAddress/dbs/${tableParams.dbName}/tables/${tableParams.tableName}`)
        	.then(response=>response.json())
        	.then(json=>dispatch(receiveTableDescriptions(tableParams, json.data))
        	).catch(e=>console.log("Fetch table names error", e))
	};
}

fetchTableDescriptions中用到的dispatch是用来连接ACTION和REDUCER的桥梁,这个方法中发送了两个ACTION, REQUESTING用来表示正在请求,会在页面上显示进度条提示,RECEIVE_TABLE_DESCRIPTIONS表示接受到数据,用来改变Table中的内容,这个行为需要一个Button来触发,这里我们就不做赘述了,触发的方式是:

import { fetchTableDescriptions } from '../actions/AppAction';

handleButtonClick(item) {
    this.props.fetchTableDescriptions({
        dbName: 'yourDbName',
        tableName: item
    });
}

3.定义Reducer

import { combineReducers } from 'redux';
import { RECEIVE_TABLE_DESCRIPTIONS,REQUESTING } from '../actions/AppAction';

/**
 * 请求的reducer
 */
function requestReducer(state = {
	loading: 'hide',
	tableItems: [],
	tableParams: {tableName: '请在左侧选择表名'}
}, action) {
	switch (action.type) {
    	case REQUESTING:
        	return Object.assign(
            	{},
            	state,
            	{
               		loading: 'loading'
            	});
    	case RECEIVE_TABLE_DESCRIPTIONS:
        	return Object.assign(
            	{},
            	state,
            	{
                	loading: 'hide',
                	tableParams: action.tableParams,
                	tableItems: action.tableItems
            	}
        	);
    	default:
        	return state;
	}
}


const rootReducer = combineReducers({
	requestReducer
});

export default rootReducer;

4.定义容器

var injectTapEventPlugin = require('react-tap-event-plugin');
injectTapEventPlugin();

import React, { Component } from 'react';
import ReactDOM from 'react-dom';
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import { Provider } from 'react-redux';
import App from './App';
import configureStore from './store/configureStore';

const store = configureStore();

class Index extends Component {

	constructor(props) {
    	super(props);
	}

	render() {
    	return (
        	<Provider store={store}>
            	<MuiThemeProvider>
               	 <App/>	
            	</MuiThemeProvider>
        	</Provider>
    	);
	}
}

ReactDOM.render(
	<Index/>,
	document.getElementById('app')
);

这里主要用来配置store,其代码内容如下:

/**
 * 配置store
 * Created by huanghuan on 16/7/21.
 */
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleWare from 'redux-thunk'; //用于做异步action
import createLogger from 'redux-logger'; //日志
import rootReducer from '../reducers/root'; //主reducer

export default function configureStore(preloadedState) {
	//创建store,并应用middleware
	const store = createStore(
    	rootReducer,
    	preloadedState,
    	applyMiddleware(thunkMiddleWare, createLogger())
	);

	//reducer的热替换?
	if (module.hot) {
    	module.hot.accept('../reducers', ()=> {
        	const nextRootReducer = require('../reducers').default;
        	store.replaceReducer(nextRootReducer);
    	})
	}

	return store;
};

结果图

在定义完所有组件后,运行的结果如下:

demo

总结

这边文章通过一个简单的例子,粗略的介绍了我对Redux的使用体会,虽然感觉ReactRedux有一定的入门成本,但一旦入门,开发的效率和效果真的非常让人满意,值得大家尝试。


上一篇博客:利用Annotation Processing生成Hibernate工具
下一篇博客:VPN搭建小记