Posted at

reduxでundo/redoをする

More than 3 years have passed since last update.


undo/redoとは??

もしかしたらundo/redoという言葉に馴染みがない人もいるかもなので・・・



  • undo = 実行した処理を取り消す(元に戻す)


  • redo = undoによって取り消した処理を再度実行する


redux-undoを使ってみる

https://github.com/omnidan/redux-undo


バージョン


  • redux-undo: 0.5.0


インストール

npm install --save redux-undo


使い方


reducerまわり

アプリケーション全体のreducerをひとまとめにしたReducerというものがあるとき、以下のようにそのReducerundoalbeで囲ってあげます


reducers.js

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;



index.js

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だからこそ成せる技、ということだと思います(actiondispatchすることでのみstateを変更することができる、などのポイントも含めてだとは思いますが)。

さて、アプリケーションの現時点のstatepresentの中におさめられているので、一部コードに修正が必要になります。以下はstatepropsに紐づける処理の中を修正しています


App.js

function mapStateToProps(state) {

const { some, hoge, fuga } = state.appData.present; // presentにする

return {
some,
hoge,
fuga
};
}



actionまわり

次にundo/redoのトリガーとなる部分の実装です。redux-undoActionCreatorsというactionを提供しています(名前がわかりづらい汗)。

以下のようにdispatchするだけでOKです


Some.js

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が実現できます


もう少しゴニョゴニョしてみる

必要ないかもしれないけどもう少しゴニョゴニョしてみる


子のreducerundoalbeを適用してみる

さきほどはアプリケーション全体のReducerundoalbeを適用したが、以下のように子(?)のreducerの1つにundoalbeを適用してみる


reducers.js

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;



index.js

import undoable from 'redux-undo';

const rootReducer = combineReducers({
router : routerStateReducer,
appData : Reducer
});



App.js

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に適用してみる


reducers.js

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変更時のものも含まれる形になります


参考

http://rackt.org/redux/docs/recipes/ImplementingUndoHistory.html