最近在看项目中报表编辑的代码,在这里写写自己对于代码的理解,整理一下思路,本文主要是写波神在报表编辑模块中应用的Redux,当然这个是简化版的,有数在这个流程中还嵌入了其他的流程,比较复杂,如果这里说法有错误的,多多包涵。
先上一张有数报表编辑的界面:
下面进入正题:
从界面中我们可以看到,在右上角有一些icon用于操作,明显整个编辑过程是支持撤销和恢复的,所以今天要说的是支持撤销和恢复的Redux,会从下面几个方面来进行整理:
通过打印store.getState()我们可以得到state的结构,这里不关心数据对象是怎样的,但是需要强调的一点是,每个state对象都应该是全新的对象,修改state不能直接state.key = value,应该类似于state = Object.assign({}, state, {key: value}, true)
{
current: Object, //当前的数据
index: Number, //当前数据在timeline数组中的索引
timeline: Array[Object], //存储时间线上的数据的数组
size: Number //限定timeline的数量
}
添加一步操作时:
撤销一步操作时:
恢复一步操作时:
Redux的middleware提供的是reducer前后的扩展点。你可以利用middleware来进行日志记录、创建崩溃报告、调用异步接口或者路由等等。
var createStore = Redux.applyMiddleware(middleware1, middleware2)( Redux.createStore )
一个middleware长这样:
function middleware() {
return function(next) {
return function(action) {
//do before reducer
var result = next(action)
//do after reducer
return result;
}
}
}
这个中间件中,有2层return,最终的效果是applyMiddleware函数会整合两个中间件,当你执行store.dispatch(action)的时候,相当于执行了下面的过程:
//do before reducer at middleware1
//do before reducer at middleware2
store.dispatch(action)
//do after reducer at middleware2
//do after reducer at middleware1
这样你就可以在dispatch之前做一些操作,像有数这边主要做的一些工作,比如在获取报表之前会先请求看这张报表是否锁定,在dispatch之后做一些错误操作的检查等等。
然后再创建store:
store = createStore(reducers, initState);
一个reducer是这样的:
function reducer(state, action) {
switch (action.type) {
case 'someType':
var newState = {};
...
return newState;
break;
default:
return state;
break;
}
}
上面是一个普通的reducer,当你想要有前进后退功能时,要对齐进行扩展,在有数中,使用了track函数来进行扩展,下面的代码是简化版,如果还需要什么功能,都是可以加的。
track函数如下:
var handlers = {
'undo': undo,
'redo': redo
}
var track = function(reducer) {
var initialState = {
timeline: [],
index: -1,
size: 24
};
return function(state, action) {
if (state === undefined) {
state = initialState;
}
var actionType = action.type;
var handler = handlers[actionType];
if (handler) {
//是回退或者恢复
return handler(state, action.payload)
}
//其他action
return add(state, reducer(state.current, action), action)
}
}
function add(state, data, action) {
var current = state.current,
nextState = {current:data};
var nextTimeline = nextState.timeline = state.timeline.slice(
state.index + 1 >= state.size ? 1 : 0,
state.index + 1
);
nextState.index = nextTimeline.push(data) - 1;
return $util.extend(newState, state) //extend是对象扩展函数,像jQuery.extend
};
function undo(state) {
return seek(state, state.index - 1)
}
function redo(sate){
return seek(state, state.index + 1)
}
function seek(state, index){
if (index < 0) index = 0;
if (index > state.timeline.length -1) index = state.timeline.length - 1;
return index === state.index ? state : $util.extend({
index: index,
current: state.timeline[index]
}, state)
}
这样的reducer就支持回退和恢复了,当需要回退时:
store.dispatch({
type: 'undo'
})
恢复时:
store.dispatch({
type: 'redo'
})
我们都知道,React结合Redux产生了react-reduxk,有数和Redux结合起来是这样的,整个报表是一个Regular组件:
var Report = Regular.extend({
name: 'report',
template: tpl,
data: {},
config: function(){},
init: function(){}
})
我们希望每次state的变化能及时地在页面中得到反映,很自然而然地想到要有一个监听回调函数:store.subscribe。
因为Regular的模版解析是在config和init之间发生的,所以将state反射到data的过程就应该放在config的最后,考虑到Report组件的很多子组件都会需要将state的数据解析到data上,所以这个方法应该封装起来被复用。
这时就用到了$afterConfig,这是Regular在实例化组件过程中会触发的一个事件,所以封装在一个mixins就变成了下面代码:
mixins.Redux = {
events: {
$afterConfig: function() {
var data = this.data;
if (this.mapStateToData) {
var unsub = store.subscribe( this.mapState.bind(this) );
this.mapState()
this.$on('$destroy', unsub);
}
}
},
mapState: function(){
var state = store.getState();
if( !state.current ) return;
this.mapStateToData(state.current, this.data, state);
setTimeout(function(){
this.$update();
}.bind(this), 0)
}
}
当你需要在组件中(报表或者其子组件)实时监听state变化的话就这样引用即可:
var Report = Regular.extend({
name: 'report',
template: tpl,
data: {},
config: function(){},
init: function(){},
/*添加mapStateToData函数*/
mapStateToData: function(currentState, data, state) {
//TODO
}
}).implement(mixin.Redux)
网易有数:企业级大数据可视化分析平台,具有全面的安全保障、强大的大数据计算性能、先进的智能分析、便捷的协作分享等特性。点击免费试用
本文来自网易实践者社区,经作者康东扬授权发布。