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

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

# 概要

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

- https://qiita.com/EBIHARA_kenji/items/25e59f7132b96cb886f3 (前回済)
- https://qiita.com/EBIHARA_kenji/items/e6da1c3d6d16cf07b60a (前回済)
- https://qiita.com/EBIHARA_kenji/items/1a043794014dc2f3a7db (途中まで前回済)

# 環境

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

# component の作成

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

```tsx: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 の作成

```shell:uuidインストール
$ npm install --save uuid && npm install --save-dev @types/uuid
```

```ts: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 を作成する

```shell:cloneインストール
npm install --save clone && npm install --save-dev @types/clone
```

```ts: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: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 を連結させる

```diff: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 に送信する

```diff: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へのレンダリング

```diff: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,
 );
+// 変更 <--
```

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

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

動いた！

![image.png](https://qiita-image-store.s3.amazonaws.com/0/215001/235d6a52-2f75-48e4-f676-6dbb8bc101ac.png)

資産構成

![image.png](https://qiita-image-store.s3.amazonaws.com/0/215001/11197453-e538-6372-4208-08a341e8707b.png)

# 感想

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

以上