undo/redoとは??
もしかしたらundo/redo
という言葉に馴染みがない人もいるかもなので・・・
-
undo
= 実行した処理を取り消す(元に戻す) -
redo
=undo
によって取り消した処理を再度実行する
redux-undo
を使ってみる
バージョン
- redux-undo:
0.5.0
インストール
npm install --save redux-undo
使い方
reducerまわり
アプリケーション全体のreducer
をひとまとめにしたReducer
というものがあるとき、以下のようにそのReducer
をundoalbe
で囲ってあげます
import { combineReducers } from 'redux';
import some from './some';
import hoge from './hoge';
import fuga from './fuga';
const Reducer = combineReducers({
some,
hoge,
fuga
});
export default Reducer;
import undoable from 'redux-undo';
const rootReducer = combineReducers({
router : routerStateReducer,
appData : undoable(Reducer) // undoalbeで囲う
});
この時点で一旦実行してみると、、、state.appData
の中身が
{
future: Array[0],
history: Object,
past: Array[2],
present: Object,
}
こんな感じになります。なるほど!!
redux
の大きな特徴の1つとして、Single State Tree
というものがあります。つまりアプリケーションのstate
を1つのオブジェクトのような形でもつ感じになります。
そのstate
を、上のfuture
だとかpast
の配列に突っ込んでは出し入れすることで、undo/redo
を実現している感じですね。
Single State Tree
だからこそ成せる技、ということだと思います(action
をdispatch
することでのみstate
を変更することができる、などのポイントも含めてだとは思いますが)。
さて、アプリケーションの現時点のstate
はpresent
の中におさめられているので、一部コードに修正が必要になります。以下はstate
をprops
に紐づける処理の中を修正しています
function mapStateToProps(state) {
const { some, hoge, fuga } = state.appData.present; // presentにする
return {
some,
hoge,
fuga
};
}
actionまわり
次にundo/redo
のトリガーとなる部分の実装です。redux-undo
はActionCreators
というaction
を提供しています(名前がわかりづらい汗)。
以下のようにdispatch
するだけでOKです
import React, { Component } from 'react';
import { ActionCreators } from 'redux-undo';
class Some extends Component {
onUndo() {
store.dispatch(ActionCreators.undo()); // undoする
}
onRedo() {
store.dispatch(ActionCreators.redo()); // redoする
}
render() {
return (
<button onClick={this.onUndo.bind(this)}>undo</button>
<button onClick={this.onRedo.bind(this)}>redo</button>
)
}
};
export default Some;
これだけでundo/redo
が実現できます
もう少しゴニョゴニョしてみる
必要ないかもしれないけどもう少しゴニョゴニョしてみる
子のreducer
にundoalbe
を適用してみる
さきほどはアプリケーション全体のReducer
にundoalbe
を適用したが、以下のように子(?)のreducerの1つにundoalbe
を適用してみる
import { combineReducers } from 'redux';
import some from './some';
import hoge from './hoge';
import fuga from './fuga';
const Reducer = combineReducers({
undoable(some),
hoge,
fuga
});
export default Reducer;
import undoable from 'redux-undo';
const rootReducer = combineReducers({
router : routerStateReducer,
appData : Reducer
});
function mapStateToProps(state) {
const { some, hoge, fuga } = state.data;
return {
some : some.present,
hoge,
fuga
};
}
この場合は、some
の中にfuture
,past
,present
などが生成される形になり、正しくundo/redo
もできるが、状態の履歴はsome
以外のstate
変更時のものも含まれる形になります
undoalbe
を複数のreducer
に適用してみる
import { combineReducers } from 'redux';
import some from './some';
import hoge from './hoge';
import fuga from './fuga';
const Reducer = combineReducers({
undoable(some),
undoable(hoge),
fuga
});
export default Reducer;
これも同じで、some
,hoge
の中にfuture
,past
,present
などが生成される形になり、正しくundo/redo
もできるが、状態の履歴はsome
,hoge
以外のstate
変更時のものも含まれる形になります
参考