概述
服务端的研发工程师或多或少也会参与一些前端的开发工作,有时候是因为前端人力的匮乏(以前实习的服务端组就没有前端,后台管理的网页都需要自己写),有时是因为自己想开发一些组内的小工具。因此,掌握一套前端开发技术,能够快速的构建网站也算是一个必备的技能。
在上述的背景下,由于组内目前采用了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的思想可以分为这几部分:
- 组件化,其实React和ES6的结合,写起来真的挺像Java,在开发过程中能够很好的进行组件的分离和复用。
- 用状态(state)和参数(props)来代表组件的内容,每一个组件都有其独立的状态,该状态受组件内部行为的影响,同时受到外部参数的影响而改变。
- 单向数据流,相比于双向绑定,实际是一个简化的思想,虽然会带来更多的代码,但流程更加清晰,问题更好追踪。
- 页面的渲染由React控制,React会根据组件state的改变情况来决定是否进行重新渲染。
所以,React相当于给了我们一个最佳实践的模版,开发者只需要关心行为和数据,完全从页面的渲染部分解放了出来。
Redux的思想
理解了React的思想,那么就我的理解而言,Redux实际上是一个管理action,state和props的工具,React让开发者需要关注用户的行为和数据,而Redux为我们提供了一个管理这两者的最佳实践模版。
Redux共由三个主要组件:
- Action 定义动作的类型,和动作中携带的参数
- Reducer 进行动作的处理,和状态的改变
- Store 相当于一个小的数据库,用来存储所有组件的状态
Redux的规定,将用户操作的行为定义为Action,这些Action都需要由Reducer去处理。如果需要获取远端的数据,可以利用fetch
和middleware
来实现。
代码实现
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;
};
结果图
在定义完所有组件后,运行的结果如下:
总结
这边文章通过一个简单的例子,粗略的介绍了我对Redux的使用体会,虽然感觉React
和Redux
有一定的入门成本,但一旦入门,开发的效率和效果真的非常让人满意,值得大家尝试。