状态历史实现时间旅行相对比较简单。以下是我将要执行的操作。
首先,我将实现一个状态历史对象,该对象维护一个过去状态数组、一个未来状态数组和一个表示现在的状态。然后我将修改该应用程序,将它的所有状态都存储在状态历史对象中,并将历史滑块和箭头按钮连接到状态历史方法。
清单 1 显示了状态历史对象。
清单 1. 状态历史对象 (statehistory.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
| export default {
past: [],
present: undefined,
future: [],
thereIsAPresent: function() { return this.present != undefined; },
thereIsAPast: function() { return this.past.length > 0; },
thereIsAFuture: function() { return this.future.length > 0; },
setPresent: function(state) { this.present = state; },
movePresentToPast: function() { this.past.push(this.present); },
movePresentToFuture: function() { this.future.push(this.present); },
movePastToPresent: function() { this.setPresent(this.past.pop()); },
moveFutureToPresent: function() { this.setPresent(this.future.pop()); },
push: function(currentState) {
if(this.thereIsAPresent()) {
this.movePresentToPast();
}
this.setPresent(currentState);
},
undo: function() {
if(this.thereIsAPresent()) {
this.movePresentToFuture(); // Moving back in time
this.movePastToPresent(); // Moving back in time
}
},
redo: function() {
if(!this.thereIsAFuture()) { // No future!
return;
}
if(this.thereIsAPresent()) {
this.movePresentToPast(); // Moving forward in time
}
this.moveFutureToPresent(); // Moving forward in time
},
gotoState: function(i) {
const index = Number(i);
const allstates = [...this.past, this.present, ...this.future];
this.present = allstates[index]
this.past = allstates.slice(0, index)
this.future = allstates.slice(index+1, allstates.length)
}
}
|
状态历史对象是一个包含以下 4 个方法的状态堆栈:
- push(state)
- undo()
- redo()
- gotoState(stateIndex)
只要图书搜索应用程序分派一个操作,它就会调用状态历史对象的 push() 方法将当前状态推送到状态历史对象的内部堆栈上。
撤销/重做对于控件组件中的撤销/重做箭头,该应用程序需要状态历史对象的 undo() 和 redo() 方法。对于历史滑块,我需要能够跳到状态历史中的任何状态,所以状态历史对象提供了一个 gotoState() 方法。
StateHistory.push() 方法将当前状态设置为现在的状态。如果开始有一个现在的状态,该方法会将它移到过去。
如果没有现在的状态,undo() 方法什么都不会做。否则,它将现在的状态移到未来,随后将过去的状态移到现在。
如果没有未来状态,redo() 方法什么也不会做;否则,它将现在的状态移到过去,随后将未来的状态移到现在。
最后,gotoState() 方法直接进入与传递给函数的索引对应的状态。gotoState() 方法使用 JavaScript 展开运算符将所有状态存储在一个数组中,然后依据索引将该数组拆分为过去、现在和未来。
介绍了状态历史对象的实现后,我将返回到图书搜索应用程序的实现上。
包含历史和撤销/重做功能的图书搜索应用程序清单 2 显示了图书搜索应用程序的 App 组件的最终实现。
清单 2. 最终的图书搜索应用程序 (containers/app.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
| import React from 'react';
import ControlsContainer from './controls';
import BooksContainer from './books';
import StateViewerContainer from './stateviewer';
const titleStyle = {
fontFamily: 'tahoma',
fontSize: '24px',
textAlign: 'center'
}
const Title = () => (
<div style={titleStyle}>
Book Search
</div>
);
export default () => (
<div>
<Title />
<hr/>
<ControlsContainer />
<BooksContainer />
<StateViewerContainer />
</div>
)
|
App 组件包含 3 个组件:ControlsContainer、BooksContainer 和 StateViewerContainer。ControlsContainer 包含历史滑块和箭头按钮,StateViewerContainer 包含一个显示当前状态的无状态组件。我首先会介绍 StateViewerContainer。
状态查看器组件清单 3 显示了状态查看器容器:
清单 3. 状态查看器容器 (containers/stateviewer.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| import { connect } from 'react-redux';
import StateViewer from '../components/stateviewer';
import stateHistory from '../statehistory';
const mapStateToProps = state => {
return {
books: state.books,
topic: state.topic,
currentStatus: state.currentStatus,
displayMode: state.displayMode,
history: stateHistory
}
}
export default connect(
mapStateToProps,
null
)(StateViewer);
|
stateviewer 容器将 5 个属性映射到它包含的无状态组件。状态查看器无状态组件在清单 4 中使用了这些属性。
清单 4. stateviewer 无状态组件 (components/stateviewer.js)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
| import React from 'react';
const StateViewer = ({
topic,
books,
currentStatus,
displayMode,
history
}) => {
const styles = {
container: {
margin: '20px',
width: '400px',
fontFamily: 'tahoma'
},
title: {
fontSize: '24px',
marginTop: '25px'
},
state: {
marginTop: '10px'
},
hr: {
marginTop: '50px'
}
};
return(
<div style={styles.container}>
<hr style={styles.hr}/>
<div style={styles.title}>
Application State
</div>
<div style={styles.state}>
Topic: {topic}<br/>
Display mode: { displayMode }<br/>
Current status: { currentStatus }<br/>
Books displayed: { books.length }<br/>
Actions processed: { history.past.length + history.future.length + 1 }<br/>
Current action: { history.past.length + 1 }
</div>
</div>
);
}
StateViewer.propTypes = {
books: React.PropTypes.array.isRequired,
currentStatus: React.PropTypes.string.isRequired,
displayMode: React.PropTypes.string.isRequired,
history: React.PropTypes.object.isRequired,
topic: React.PropTypes.string.isRequired,
};
export default StateViewer;
|
清单 4 中的组件很简单;它仅显示自己的属性,这些属性来自包含它的 StateViewerContainer,该容器从当前状态获得它们。
这就是在页面底部显示应用程序状态的状态查看器。接下来我返回到更有趣的控件组件。 |