React-Reduxを広域stateとして利用する

# React-Reduxを広域stateとして利用する

　Reduxを利用する上で面倒なのは、データ操作の種類ごとに、逐一手続きを書かなければならないことです。そのように書いておけば、データの操作がどのように作用するのか、それを追う上では役に立つでしょう。しかし、やりたいことがただ単に、コンポーネントにデータを配りたいというだけの場合、ひたすら手続きを書くのは面倒な作業でしかありません。

　ということでこの面倒な作業、Actionの記述とReducerの処理を省略する方法を考えてみました。


## １．Reducerに処理関数を送りつける構造を作る

　確実に邪道ですが、Actionにはコールバック用の関数を載せて、Reducerで呼び出すという構造をとります。関数の中にはデータを操作する機能を入れる形です。そして、もはやコールバックを書くことすら面倒なので、それも自動で作ってくれるsetStoreStateという関数も用意します。息をするのも面倒くさい感じです。

　Storeデータを書き換える単一機能なら、わざわざコールバックさせなくても良さそうな感じですが、もっと複雑な機能を実装する場合にカスタマイズする余地が残ります。

```CallbackReducer.ts
interface State {
  [key: string]: unknown;
}
interface Action {
  type: string;
  payload: {
    callback: (state: State) => State;
  };
}

/**
 *送られてきた関数を実行するだけのReducer
 *
 * @export
 * @param {*} [state={}]
 * @param {Action} action
 * @returns
 */
export function CallbackReducer(state = {}, action: Action) {
  if (action.type === "CALLBACK") return action.payload.callback(state as never);
  return state;
}

/**
 *Storeにデータを設定する
 *
 * @export
 * @param {(action: Action) => void} dispatch
 * @param {string} name Storeの一層目のオブジェクト名を指定
 * @param {{ [key: string]: unknown }} params パラメータ
 */
export function setStoreState(
  dispatch: (action: Action) => void,
  name: string,
  params: { [key: string]: unknown }
) {
  dispatch({
    type: "CALLBACK",
    payload: {
      callback: state => {
        const newState = { ...state };
        newState[name] = { ...newState[name], ...params };
        return newState;
      }
    }
  });
}
```

## ２．実際に使ってみる

　setStoreStateがデータ操作用のコールバックを生成しReducerに実行させることによって、Storeの値を変更しています。コンポーネントをまたいでいることを確認するために、コンポーネントを二つ用意していますが、ボタンのクリックイベントで両方とも表示結果が変わります。

　Stateに関しては、オブジェクトの名前がパッティングしないようにすれば、複数のコンポーネントのデータを保存することも可能です。

　ちなみにデータの初期化はあえてやっていませんが、createStoreで初期値を持たせることはもちろん可能です。


```tsx:index.tsx
import React from "react";
import * as ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider, useDispatch, useSelector } from "react-redux";
import { CallbackReducer, setStoreState } from "./CallbackReducer";

interface State {
  test: { msg: string; count: number };
}

function AppComponent() {
  const dispatch = useDispatch();
  const test = useSelector((state: State) => state.test) || {msg:"初期状態",count:0};
  return (
    <div>
      <div>AppComponent</div>
      <button onClick={() =>
          setStoreState(dispatch, "test", { msg: "クリック", count: test.count + 1 })
        }>Button</button>
      <div>{test.msg}</div>
      <div>{test.count}</div>
    </div>
  );
}
function AppComponent2() {
  const test = useSelector((state: State) => state.test) || {msg:"初期状態",count:0};
  return (
    <div style={{marginTop:"1em"}}>
      <div>AppComponent2(コンポーネントをまたいでいるのを確認)</div>
      <div>{test.msg}</div>
      <div>{test.count}</div>
    </div>
  );
}

const Store = createStore(CallbackReducer);
function App() {
  return (
    <Provider store={Store}>
      <AppComponent /><AppComponent2 />
    </Provider>
  );
}
ReactDOM.render(<App />, document.getElementById("root") as HTMLElement);
```

## ３．実行結果
![Microsoft Edge 2019-09-06 23-01-19.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267629/12d8179b-96a6-b12a-8167-05c4e6f15d54.gif)


## ４．まとめ

　何となく出来るかなと思って作ってみたら、意外に問題なく動いたので記事にしてみました。もちろんconnectしたclassコンポーネントでも動きます。

　本当はコールバック主体の機能だったのですが、オブジェクト名を指定してStoreデータを書き換える関数を作ったら、そちらが便利だったので全部持って行かれた感じです。

　面倒な手続きをすっ飛ばして、広域setStateを使いたいという方は試してみてください。ただし王道を行く人にはお勧めしません。