Electron & React & Redux & TypeScript アプリ作成ワークショップ をやってみた2


https://qiita.com/y_ohr/items/5c18dd1621b5342a05ea の続き。

概要

以下をやってみた記録。良記事に感謝。

環境

Node.jsとnpmのバージョン
$ node -v
v10.13.0
$ npm -v
6.4.1

component の作成

ユーザー名入力画面の作成

ts/components/UserForm.tsx
import React from 'react';
import IUser from '../states/IUser';
import { TextBox } from './TextBox';

/**
 * ユーザ名を入力して表示する
 */
class UserForm extends React.Component<IUser, {}>{
    public render() {
        return (
            <div>
                <p>
                    <TextBox label="ユーザー名" type="text" value={this.props.name}
                        onChangeText={this.onChangeText} />
                </p>
                <p>名前: {this.props.name}</p>
            </div>
        );
    }

    private onChangeText = (value: string) => {
        // action や store ができてから書く
    }
}

action と action creator の作成

uuidインストール
$ npm install --save uuid && npm install --save-dev @types/uuid
ts/actions/UserNameEvents.ts
import Redux from 'redux';
import { v4 as UUID } from 'uuid';

/**
 * ユーザー名を変更するアクション・タイプ
 */
export const CHANGE_USER_NAME = UUID();

/**
 * ユーザー名を変更するアクション
 */
export interface IChangeUserNameAction extends Redux.Action {
    /** 変更する名前の文字列 */
    name: string;
}

/**
 * ユーザー名変更アクション・クリエイター
 * @param name 変更する名前の文字列
 * @return ユーザー名変更アクション
 */
export const createChangeUserNameAction: Redux.ActionCreator<IChangeUserNameAction> = (name: string) => {
    return {
        name,
        type: CHANGE_USER_NAME,
    };
};

reducer を作成する

cloneインストール
npm install --save clone && npm install --save-dev @types/clone
ts/reducers/UserReducer.ts
import Clone from 'clone';
import Redux from 'redux';

import { CHANGE_USER_NAME, IChangeUserNameAction } from '../actions/UserNameEvents';
import IUser, { initUser } from '../states/IUser';

export const UserReducer: Redux.Reducer<IUser> = (childState = initUser, action) => {
    let newChildState: IUser = childState;
    switch (action.type) {
        case CHANGE_USER_NAME:
            {
                newChildState = Clone(childState);
                newChildState.name = (action as IChangeUserNameAction).name;
            }
            break;
    }
    return newChildState;
};

store を作成する

ts/Store.ts
import { combineReducers, createStore } from 'redux';
import { UserReducer } from './reducers/UserReducer';
import IUser from './states/IUser';

/**
 * store のデータ型を定義する。(親state)
 * 
 * プロパティには、管理する child_state を指定する
 */
export interface IState {
    User: IUser;
    // state が増えたら足していく
}

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

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

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

store と component を連結させる

ts/components/UserForm.tsx
diff --git a/ts/components/UserForm.tsx b/ts/components/UserForm.tsx
index 36a22f1..6c3f614 100644
--- a/ts/components/UserForm.tsx
+++ b/ts/components/UserForm.tsx
@@ -1,5 +1,7 @@
 import React from 'react';
+import { connect, MapStateToPropsParam } from 'react-redux'; // 追加
 import IUser from '../states/IUser';
+import { IState } from '../Store'; // 追加
 import { TextBox } from './TextBox';

 /**
@@ -22,3 +24,9 @@ class UserForm extends React.Component<IUser, {}>{
         // action や store ができてから書く
     }
 }
+// 追加 -->
+const mapStateToProps = (state: IState) => {
+    return state.User;
+};
+export default connect(mapStateToProps)(UserForm);
+// <- 追加

component から action を reducer に送信する

ts/components/UserForm.tsx
diff --git a/ts/components/UserForm.tsx b/ts/components/UserForm.tsx
index 6c3f614..a51d8f8 100644
--- a/ts/components/UserForm.tsx
+++ b/ts/components/UserForm.tsx
@@ -1,7 +1,8 @@
 import React from 'react';
 import { connect, MapStateToPropsParam } from 'react-redux'; // 追加
 import IUser from '../states/IUser';
-import { IState } from '../Store'; // 追加
+import { createChangeUserNameAction } from '../actions/UserNameEvents'; // 追加
+import store, { IState } from '../Store'; // 変更
 import { TextBox } from './TextBox';

 /**
@@ -21,7 +22,7 @@ class UserForm extends React.Component<IUser, {}>{
     }

     private onChangeText = (value: string) => {
-        // action や store ができてから書く
+        store.dispatch(createChangeUserNameAction(value));
     }
 }
 // 追加 -->

HTMLへのレンダリング

ts/index.tsx
diff --git a/ts/index.tsx b/ts/index.tsx
index c25e4e5..58d6af1 100644
--- a/ts/index.tsx
+++ b/ts/index.tsx
@@ -1,9 +1,15 @@
 import React from 'react';
 import ReactDom from 'react-dom';
+import { Provider } from 'react-redux'; // 追加
+import UserForm from './components/UserForm'; // 追加
+import Store from './Store'; // 追加

 const container = document.getElementById('contents');
-
+// 変更 -->
 ReactDom.render(
-    <p>こんにちは、世界</p>,
+    <Provider store={Store}>
+        <UserForm />
+    </Provider>,
     container,
 );
+// 変更 <--

ビルドして動作確認する。

webpack実行とelectron起動
$ npm run build
$ npm start

動いた!

資産構成

感想

  • ちゃんと動いて感動した
  • Reduxに慣れが必要
  • クラスや定数のimportが縦横無尽で追えなくなる。脳内マップが必要。
  • ライブラリに追加したuuidcloneは一般的なベストプラクティスなのだろうか?
  • componentなどのUI関連クラスと、actionなどのRedux関連クラスは、もう少しフォルダ等を整理して、関心を分離できないのだろうか?こういうものなのだろうか。
  • VSCodeでソースを整形しても、tslintに警告されないようにしたい。

以上