LoginSignup
25
15

More than 3 years have passed since last update.

Electron & React & Redux & TypeScript アプリ作成ワークショップ 4日目

Last updated at Posted at 2018-08-21

概要

前回はごく簡単な React-Redux を使ったアプリを作りました。

今回から、数回に渡ってより実践的なアプリを作ってみましょう。

最終的には、ファイルから非同期でデータの入出力を行うアプリにしますが、その前に

長くなるので、Component は次回にして、その他のモジュールを作成していきます。

作成するアプリの仕様

ToDo を管理するアプリを作成します。画面仕様はこんな感じです。

4.todo_1-1.png

この画面のイベントとしては、下記があります。

  • タスク一覧の表示
  • タスクの追加
  • タスクの完了・未完了の切り替え
  • タスクの削除

プロジェクトの準備

空のディレクトリを作成し、前回作成したチュートリアルのファイルをコピーします。

dist と node_modules は対象外とします。

新しいディレクトリで VSCode を開いて、下記を行います。

  • package.json を開いて、プロジェクトの名前を変更します。
  • ターミナルでnpm installを実行して、ライブラリをインストールします。

src ディレクトリ以下の下記ファイルを削除します。ディレクトリは残しておいてください。

ts
│
├─actions
│      UserActions.ts // 削除
│
├─components
│      CountButton.tsx // 削除
│      TextBox.tsx // 削除
│      UserForm.tsx // 削除
│
├─reducers
│      UserReducer.ts // 削除
│
└─states
        IUser.ts // 削除

ライブラリをインストールしておきます。

$ npm install
# or
$ yarn

各ファイルの関係

各モジュールの関連は下記のようになります。

4.todo_2.png

子 State 定義の作成

Redux で管理するデータの定義を作成します。

TODO ですので、id と、タスクの名前、期限、完了したかどうかのフラグを持ちましょう。

タスクは複数管理されるので、その配列を保持するインターフェイスも定義しておきます。

また、ステートの新規作成するときの関数と、その一覧のオブジェクトの初期値も定義しておきます。

ITask.ts を作成します。

src/states/ITask.ts
/**
 * タスク
 */
export interface ITask {
  /** 完了フラグ */
  complete: boolean;
  /** 期限 */
  deadline: Date;
  /** タスクを一意に判断するID (UUID) */
  id: string;
  /** タスクの名前 */
  taskName: string;
}
/**
 * タスクのリスト
 */
export interface ITaskList {
  /** タスクの一覧 */
  tasks: ITask[];
}

統合する State も修正しておきます。

src/states/IState.ts
import { ITaskList } from './ITask';

export interface IState {
  taskList: ITaskList;
}

State の作成

Reducer を合成、ステートとなるインターフェースの定義、ステートの作成をします。

src/Store.ts
import { combineReducers, createStore } from 'redux';
import { IState } from './states/IState';
import taskReducer from './reducers/TaskReducer'; // 後からファイルを追加する

// 複数の reducer を束ねる
const combinedReducer = combineReducers<IState>({
  taskList: taskReducer,
  // reducer が増えたら足していく
});

// グローバルオブジェクトとして、store を作成する。
export const store = createStore(combinedReducer); // --(b)

// import store from './Store' とアクセスできるように default として定義する
export default store;

combineReducers がエラーになっていますが、新しい Reducer を作成したときに修正することにします。

Action の作成

画面のイベントをアクションとして作成していきます。

タスクの一覧の取得処理を関数で書いています。これは後ほど、ファイルから非同期で取得するための準備です。

momentという日付処理のライブラリを利用しています。下記でインストールします。

$ npm install --save moment && npm install --save-dev @types/moment
# or
$ yarn add moment && yarn add -D @types/moment
src/action/TaskActions.ts
import moment from 'moment';
import { Dispatch } from 'redux';
import { actionCreatorFactory } from 'typescript-fsa';
import { ITask } from '../states/ITask';

const actionCreator = actionCreatorFactory('task-actions');

/**
 * タスクの一覧を表示する
 * 引数は、タスクオブジェクトの配列
 */
export const showTaskListAction = actionCreator<ITask[]>('show-task-list');

/**
 * タスクを追加する
 * 引数は、追加するタスクオブジェクト
 */
export const addTaskAction = actionCreator<ITask>('add');

/**
 * タスクの完了フラグをトグルする
 * 引数は、タスクID
 */
export const toggleCompleteAction = actionCreator<string>('toggle-complete');

/**
 * タスクを削除する
 * 引数は、タスクID
 */
export const deleteTaskAction = actionCreator<string>('delete');

// 確認のため、ダミーデータをハードコーディングする
const dummyTasks: ITask[] = [
  {
    complete: false,
    deadline: moment().add(1, 'day').toDate(),
    id: '0',
    taskName: 'task01',
  },
  {
    complete: true,
    deadline: moment().add(1, 'day').toDate(),
    id: '1',
    taskName: 'task02',
  },
  {
    complete: false,
    deadline: moment().add(-1, 'day').toDate(),
    id: '2',
    taskName: 'task03',
  },
  {
    complete: true,
    deadline: moment().add(-1, 'day').toDate(),
    id: '3',
    taskName: 'task04',
  },
];

/** タスクの一覧を取得する */
export const getTaskList = (dispatch: Dispatch): void => {
  // 別の回で非同期の処理に変更するため関数としている。
  dispatch(showTaskListAction(dummyTasks));
};

Reducer の作成

作成したアクション毎の Reducer の処理を書いていきます。

src/reducers/TaskReducer.ts
import { reducerWithInitialState } from 'typescript-fsa-reducers';
import { ITaskList } from '../states/ITask';
import {
  showTaskListAction,
  addTaskAction,
  deleteTaskAction,
  toggleCompleteAction,
} from '../actions/TaskActions';

const initState: ITaskList = {
  tasks: [],
};

const taskReducer = reducerWithInitialState<ITaskList>(initState)
  // タスクリストを表示する
  .case(showTaskListAction, (state, payload) => ({
    ...state, // tasks しか無いので本来は必要ないが、プロパティが増えた場合に必要
    tasks: payload,
  }))
  // タスクを追加する
  .case(addTaskAction, (state, payload) => {
    const taskList = [...state.tasks, payload];
    return {
      ...state,
      tasks: taskList,
    };
  })
  // タスクを削除する
  .case(deleteTaskAction, (state, payload) => {
    const taskList = state.tasks.filter(task => task.id !== payload);
    return {
      ...state,
      tasks: taskList,
    };
  })
  // 完了フラグを反転する
  .case(toggleCompleteAction, (state, payload) => {
    const taskList = [...state.tasks];
    const task = taskList.find(task => task.id === payload);
    // 見つからなければ、何もしない。(現在のステートを返す)
    if (!task) {
      return state;
    }
    task.complete = !task.complete;
    return {
      ...state,
      tasks: taskList,
    };
  })
  .build();

export default taskReducer;

次回

次回は、 CSS in JavaScript の解説と、Component を作っていきます。

25
15
2

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
25
15