LoginSignup
8
11

More than 5 years have passed since last update.

reduxでundo/redoをする

Posted at

undo/redoとは??

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

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

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

参考

8
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
11