React-ReduxだけどReducerもActionも書かず、Dispatchすら使わず、データも何となく受け取れるようにする方法

# React-ReduxだけどReducerもActionも書かず、Dispatchすら使わず、データも何となく受け取れるようにする方法

前回
[Reduxがあまりにも面倒なので、何もかも隠蔽しつつ、その力を引き出すことにした](https://qiita.com/SoraKumo/items/99f22ac39baf8bd0c93a)

## とにかく面倒くさいFlux

　React上での状態管理は、ことごとくがFluxの考え方を用いて作られています。

　Actionのコマンドを定義してDispatchで送信し、Reducerで処理してStoreに書き込み、ViewがStoreの更新を察知して処理を行うという流れです。これらの処理の厄介なところは、全部が全部、記述箇所がバラバラになってしまうということです。

　とにかく何もかも面倒くさいので、この地獄から抜け出す方法を探しました。結果として、上記に書いたようなことを一切書かず、簡単にStoreデータを操作する方法にたどり着きました。


##　環境設定と必要なパッケージ

今回のサンプルプログラムを動かすにはReact+TypeScript環境を用意してください。
また、以下のパッケージをインストールする必要があります。

```
npm -D i @jswf/redux-module
```

## サンプルプログラム

　HooksのFunctionコンポーネントと、Classコンポーネントでの利用法を同時に載せているので、プログラムが少々長くなっています。
　中でやっているのはinputの内容をコンポーネント間で共有するというものです。
　もしこれをやるためだけにReduxの書式を真面目に書いたら、確実に無駄地獄へ落ちることが出来るでしょう。

　コンポーネント間のデータ共有にはReduxModuleというクラスを継承して利用します。
　クラスを作ると、クラスごとに一つStore領域が割り当てられます。
　そのため同じクラスを使用する限り、同じデータを参照することが出来ます。
　また、読み書きの受付も全てこのReduxModule継承クラスが担当するので、処理をあちこちに書く必要はありません

![Microsoft Edge 2019-09-06 23-01-19.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/267629/ad47971d-d637-2819-8095-1e8d9a04b1cb.gif)

[https://github.com/JavaScript-WindowFramework/redux-module-sample](https://github.com/JavaScript-WindowFramework/redux-module-sample)

```tsx:index.tsx
import React, { Component } from "react";
import * as ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import {
  ModuleReducer,
  useModule,
  ReduxModule,
  mapModule,
  mapConnect
} from "@jswf/redux-module";

/**
 *データ構造の定義(TypeScript使用時)
 *
 * @export
 * @interface TestState
 */
export interface TestState {
  msg: string;
}
/**
 *Storeアクセス用クラス
 *(クラスごとに自動的にStoreに領域を確保する)
 * @export
 * @class TestModule
 * @extends {ReduxModule<TestState>}
 */
export class TestModule extends ReduxModule<TestState> {
  //ここに初期値を設定可能
  protected static defaultState: TestState = {
    msg: "初期値"
  };
  //以下のようなアクセス用のメソッドは、必ずしも作る必要は無い
  //getStateとsetStateはpublicなので、外から直接書き換えてしまってもOK
  public getMessage() {
    return this.getState("msg")!;
  }
  public setMessage(msg: string) {
    this.setState({ msg });
  }
}

/**
 *Hooks用サンプル
 *
 * @returns
 */
function HooksApp() {
  //モジュールのインスタンスを受け取る
  //useModuleの使用可能場所の制限は他のhookと同じ
  const testModule = useModule(TestModule);
  //以下のようにPrefixを付けると、同じクラスが違う領域を持つことも出来る
  //const testModule = useModule(TestModule,"Prefix");
  return (
    <>
      <div>FunctionComponent</div>
      <input
        value={testModule.getMessage()}
        onChange={e => testModule.setMessage(e.target.value)}
      />
      <hr />
    </>
  );
}

/**
 *Class用サンプル
 *
 * @class _ClassApp
 * @extends {Component}
 */
class _ClassApp extends Component {
  render() {
    //モジュールのインスタンスを受け取る
    //Hooksと名前と引数が微妙に違うので注意
    const testModule = mapModule(this.props, TestModule);
    return (
      <>
        <div>ClassComponent</div>
        <input
          value={testModule.getMessage()}
          onChange={e => testModule.setMessage(e.target.value)}
        />
        <hr />
      </>
    );
  }
}
//クラスコンポーネントを利用する場合は以下の方法でマッピングする
//ここで宣言したモジュール以外はクラスで使用できない
//モジュールは配列で複数指定も可能
const ClassApp = mapConnect(_ClassApp, TestModule);

//Reduxに専用のReducerを関連付ける
//他のReducerと併用することも可能
const store = createStore(ModuleReducer);
ReactDOM.render(
  <Provider store={store}>
    <HooksApp />
    <ClassApp />
  </Provider>,
  document.getElementById("root") as HTMLElement
);
```

##　必要なことのまとめ

1 ModuleReducerをReduxのStoreに関連付ける
2 ReduxModuleを継承したデータクラスを作成する
3 Classコンポーネントを使う場合はmapConnectで使用するデータクラスを関連付ける
4 useModule/mapModuleでデータクラスを呼び出す
5 setState/getStateでデータの読み書きを行う



## 一応解説しておく付加機能

　蛇足になるので最小限にとどめますが、データクラスは外部参照機能も付いています。
　別のデータクラスの機能が必要な場合はincludesに利用するモジュールを指定しておけば、getModuleで対象のクラスを呼び出すことが出来ます。

```tsx
export class OtherModule extends ReduxModule {
  static includes = [TestModule]
  public getMessage() {
    return this.getModule(TestModule).getState("msg")!;
  }
  public setMessage(msg: string) {
    this.getModule(TestModule).setState({ msg });
  }
}
```

## まとめ

　とにかく状態管理が楽になりました。これを使うことによって、コンポーネント間の手続きの大部分が省略可能となります。コンポーネント間のデータ共有にReduxを使いたいけれど、Flux的な書き方が嫌だと考えているのならぜひ使ってみてください。