Reduxがあまりにも面倒なので、何もかも隠蔽しつつ、その力を引き出すことにした
1.Reduxはまともにやってられない
ReduxをFlux流儀に従って真面目に取り扱っていると、記述量が増えてやっていられなくなります。コンポーネントに「データを配りたいだけなのに、なんでこんな回りくどいことをいなければならないんだ」と思っている人は一定数いることでしょう。
ということでStoreもActionもReducerもDispatchも、なんだかよく分からなくても使えるモジュールを作成しました。冗長な記述を極限まで減らし、データの出し入れに特化しました。もちろん複雑な操作が必要となれば、拡張可能な余地を残してあります。
前提としてReact上でHooksが必須でサンプルはTypeScriptを使用しています。型を使わなければJavaScriptでも動作するはずですが試していません。
2.インストール
Reactの動作環境が整っているのが前提です。
npm -D i @jswf/redux-module
3.利用手順の部分解説
3.1 ストアの登録
一応Reduxを利用したモジュールなのでReducerの登録が必要となります。もちろん他のReducerとの併用や、Middlewareを動かすことも可能です。
//Store create
const store = createStore(ModuleReducer);
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById("root") as HTMLElement
);
3.2 ストア操作用モジュールの作成
データの操作用にモジュールクラスを作る必要があります。このクラスがRedux操作を全て隠蔽します。
defaultStateは作らなくても問題ありませんが、そのかわりsetStateするまでundefinedが返ります。
このクラスにメソッドを作って入出力機能を入れても構いません。
その場合はthis.setStateやthis.getStateを利用してください。
//Store module data type
export interface State {
msg: string;
count: number;
}
export class TestModule extends ReduxModule<State> {
//init value
static defaultState: State = {
msg: "init",
count: 0,
other:{
v:123
}
};
}
3.3 データ操作方法
今回の機能はHooksが前提となるので、クラスコンポーネントでは利用できません。クラスコンポーネントにはそのうち対応するかもしれません。
データの書き込みは最上位階層でマージが行われます。下の階層をマージしたい場合は、setStateの第二引数以降で場所を指定してください。
//Use modules
const module = useModule(TestModule);
const module2 = useModule(TestModule,"prefix"); //Different Store area is used
//Get data
const value = module.getState()!; //{msg:"init",count:0}
const v = module.getState("other","v"); //123
//Set data
module.setState({ msg: "click!", count: value.count + 1 });
//Only one is acceptable
module.setState({count: value.count + 1 });
//The object location can be described later
module.setState(1000,"other","v"); //write to {other:{v:1000}}
4.最小限のサンプルアプリ
Reduxのサンプルは長くなりがちなので、極力切り詰めました。とはいってもこれがReduxを使ったプログラムだとは思わないでしょう。特徴がことごとく存在しません。
やっていることはStateで型情報の定義(TypeScriptを使う場合)とReduxModuleを継承したTestModuleとデフォルト値の設定です。これによって自動的にStore領域にアクセスする機能を持つことができます。
モジュールの機能を利用する場合はuseModuleを利用します。必要に応じてModuleのインスタンスが生成され、Storeにアクセス可能となります。
データの読み書きはsetStateとgetStateで行います。一般的にデータの操作にはDispatchが必要ですが、隠蔽しているので出てきません。useSelectorも隠しました。何の話をしているか分からない人は分からないままで大丈夫です。
ちなみに複雑な操作を伴う機能が必要となった場合は、作成したモジュールクラスにメソッドとして追加していけば大抵のことには対応可能です。今回はサンプルを分かりやすくするためにあえてやっていません。
ソースコード
import React from "react";
import * as ReactDOM from "react-dom";
import { createStore } from "redux";
import { Provider } from "react-redux";
import { ModuleReducer, useModule, ReduxModule } from "@jswf/redux-module";
//Store module data type
interface State {
msg: string;
count: number;
}
//ReduxのStoreデータを扱うクラスを作成する
//操作するStoreデータの場所は同じクラスで共有される
class TestModule extends ReduxModule<State> {
//init value
static defaultState: State = {
msg: "init",
count: 0
};
}
function App() {
//モジュールを呼び出す(第二引数でprefixを付けると、Store領域を分離できる)
const module = useModule(TestModule/*,"prefix"*/);
//Get data
const value = module.getState()!;
return (
<div style={{border:"solid 1px",display:"inline-block",padding:"1em"}}>
<div>AppComponent</div>
<button onClick={() => module.setState({ msg: "click!", count: value.count + 1 })}>
button</button>
<div>{value.msg}</div>
<div>{value.count}</div>
</div>
);
}
//Store create
const store = createStore(ModuleReducer);
ReactDOM.render(
<Provider store={store}>
<App /><App />
</Provider>,
document.getElementById("root") as HTMLElement
);
5.まとめ
Reduxの状態保持とMiddlewareの機能は使いたいけどFlux的な記述が嫌だ、息を吸うのも面倒くさい、という自分のために作りました。Fluxの手順を隠蔽することによって、データ操作がかなり楽になっています。
今回のモジュールはTypeScript3.6固有の再帰型の階層が深いとすぐエラーが出る問題の絡みで、一部の型チェックを省いています。TypeScript3.7が公式リリースされたら、修正を入れたいと思っています。