Reduxが難しい? それは過去の話だ! ~ ToDoアプリを最小限の労力で記述する ~
React-Reduxを簡単に利用するためのパッケージ@jswf/redux-moduleを利用して、最小限の労力でToDoアプリを作成するという内容です。
※追記
@jswf/redux-moduleの使い方は以下記事で、もう少し短いソースにて確認できます
React-ReduxだけどReducerもActionも書かず、Dispatchすら使わず、データも何となく受け取れるようにする方法
最初に
プログラムはファイル一つだけにまとめました。
内容は以下のように構成されています。
- データ構造の定義
- データ操作用クラスの作成
- データ入力用コンポーネント
- データ表示用コンポーネント
今回はReduxラッパーの機能を使うため、データの入力と表示はコンポーネントは分けてあります。
ここで見ていただきたいのは、propsもuseStateも使っていないということです。
全てのデータはReduxのStore上に格納されます。
しかしReduxを使う上での前提となるFlux的な定義は一切書く必要がありません。
ReduxModuleクラスを継承することによって、読み書き全ての手続きがReduxModuleで定義されているsetStateとgetStateメソッドに集約されます。
作ったもの
ソースコード
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";
/**
*データ構造の定義(TypeScript使用時)
*
* @export
* @interface TodoState
*/
interface TodoState {
//入力中データの保持
input: {
title: string;
desc: string;
};
//TODOリスト
todos: {
id: number;
title: string;
desc: string;
done: boolean;
}[];
//TODOのID附番表index
index: number;
}
/**
*Todoデータ管理用クラス
*
* @export
* @class TodoModule
* @extends {ReduxModule<TestState>}
*/
export class TodoModule extends ReduxModule<TodoState> {
//初期値
protected static defaultState: TodoState = {
todos: [],
input: { title: "", desc: "" },
index: 0
};
/**
*ToDoリストを返す
*
* @returns
* @memberof TodoModule
*/
public getTodos() {
return this.getState("todos")!;
}
/**
*ToDoを追加(追加データはStoreに入っているので引数不要)
*
* @memberof TodoModule
*/
public addTodo() {
//indexのインクリメント
const index = this.getState("index")! + 1;
this.setState({ index });
//必要データの読み出し
const title = this.getState("input", "title")!;
const desc = this.getState("input", "desc")!;
//todoを追加
const todos = [
...this.getState("todos")!,
{ id: index, title, desc, done: false }
];
this.setState({ todos })!;
}
/**
*ToDoの状態管理
*
* @param {number} id
* @param {boolean} done
* @memberof TodoModule
*/
public updateDone(id: number, done: boolean) {
const srcTodos = this.getState("todos")!;
//状態の書き換え
const todos = srcTodos.map(todo =>
todo.id === id ? { ...todo, done } : todo
);
this.setState({ todos });
}
/**
*ToDoの削除
*
* @param {number} id
* @memberof TodoModule
*/
public remove(id: number) {
const srcTodos = this.getState("todos")!;
//削除データを除外
const todos = srcTodos.filter(todo => todo.id !== id);
this.setState({ todos });
}
}
/**
*入力フォームコンポーネント
*
* @returns
*/
function FormComponent() {
const todoModule = useModule(TodoModule);
return (
<div style={{ textAlign: "center" }}>
<div>
<div>タイトル</div>
<input
style={{ width: "20em" }}
value={todoModule.getState("input", "title")!}
onChange={e => todoModule.setState(e.target.value, "input", "title")}
/>
<div>説明</div>
<textarea
style={{ width: "20em", height: "5em" }}
value={todoModule.getState("input", "desc")!}
onChange={e => todoModule.setState(e.target.value, "input", "desc")}
/>
<div>
<button onClick={() => todoModule.addTodo()}>Todoを作成</button>
</div>
</div>
</div>
);
}
/**
*ToDo出力コンポーネント
*
* @returns
*/
function TodoListComponent() {
const todoModule = useModule(TodoModule);
const todos = todoModule.getState("todos")!;
return (
<div style={{ display:"flex",flexDirection: "column" ,alignItems:"center"}}>
{todos.map(todo => (
<div
key={todo.id}
style={{
display: "inline-block",
width: "20em",
marginTop: "1em",
border: "solid 1px",
textAlign:"center"
}}
>
<div>
{todo.id}:{todo.title}{" "}
<span
style={{ cursor: "pointer" }}
onClick={() => todoModule.updateDone(todo.id, !todo.done)}
>
{todo.done ? "完了" : "未完了"}
</span>
{todo.done && (
<span
style={{ cursor: "pointer" }}
onClick={() => todoModule.remove(todo.id)}
>
削除
</span>
)}
</div>
<div>{todo.desc}</div>
</div>
))}
</div>
);
}
//Reduxに専用のReducerを関連付ける
//他のReducerと併用することも可能
const store = createStore(ModuleReducer);
ReactDOM.render(
<Provider store={store}>
<FormComponent />
<TodoListComponent />
</Provider>,
document.getElementById("root") as HTMLElement
);
まとめ
ほぼReduxの影や形が消え去っています。
今回の内容はコンポーネント間の連係や、データをStoreに集約することが目的であれば、かなり便利に使えると思います。
パフォーマンスを考えて作る場合は、副作用の影響範囲を最小限に抑えるためデータ操作用のクラスを細分化したり、書き込みのみしか利用しないコンポーネント上ではwriteOnly属性を付けたりとチューニングが必要になりますが、それは別の記事で解説を入れたいと思います。
Reduxの定義に疲れ果てた方はぜひ使ってみてください。