70
87

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

React-Reduxが難しい? それは過去の話だ! ~ ToDoアプリを最小限の労力で記述する ~

Last updated at Posted at 2019-10-02

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メソッドに集約されます。

作ったもの

実働サンプル
GitHub上のソース
screenshot.gif

ソースコード

index.tsx
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の定義に疲れ果てた方はぜひ使ってみてください。

リンク

70
87
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
70
87

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?