個人的React周りベストプラクティス⑥ - 開発編

#前の記事
前回：[個人的React周りベストプラクティス⑤ - Reduxディレクトリ構成編](https://qiita.com/enshi/items/c60bb9055623ae318377)
一番始め：[個人的React周りベストプラクティス① - 構成編](https://qiita.com/enshi/items/e6ad8c91168cfe8a88da)

#フロントサイド
##新規PJ作成
###:one: create-react-appで基本的なReactアプリを作成する
```shell-session:
$ create-react-app redux_hoge
```
redux_hogeというディレクトリの中に色々と作られる

###:two: 必要パッケージ、ミドルウェアインストール
参考：[個人的React周りベストプラクティス③ - Reduxパッケージ・ミドルウェア編](https://qiita.com/enshi/items/3802f48cbcb05d028738)

```shell-session:
$ cd redux_hoge
$ npm install redux react-redux
$ npm install axios redux-actions material-ui react-tap-event-plugin react-router-dom redux-promise redux-logger
```

###:three: デフォルトで作られたファイルはいらないので、src内の全ファイル削除
```shell-session
$ rm -f src/*
```

###:four: 空のディレクトリを作成する
参考：[個人的React周りベストプラクティス⑤ - Reduxディレクトリ構成編](https://qiita.com/enshi/items/c60bb9055623ae318377)

```shell-session
$ mkdir -p src/actions src/components/App src/constants src/css src/lib src/reducers
```

###:five: index.jsを作成する
```js:src/index.js
import React from 'react';
import { render } from 'react-dom';
import { applyMiddleware, createStore } from 'redux';
import { Provider } from 'react-redux';
import { BrowserRouter } from 'react-router-dom';

import './css/reset.css';	//reset.css
import './css/common.css';	//common.css
import Allreducer from './reducers';    //reducerをcombineReducersで合体しているファイル
import App from './components/App/App';      //ルートコンポーネント

//Middleware
import promiseMiddleware from 'redux-promise';	//非同期処理用
import { createLogger } from 'redux-logger';	//ログ取得用

//Material-Ui
import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider';
import injectTapEventPlugin from "react-tap-event-plugin";
injectTapEventPlugin(); //タッチイベント実行用


//reducerとミドルウェアを指定してstore作成
let store = createStore(
    Allreducer,
    applyMiddleware(promiseMiddleware, createLogger())
);


render(
  <MuiThemeProvider>
    <Provider store={store}>
      <BrowserRouter>
        <App />
      </BrowserRouter>
    </Provider>
  </MuiThemeProvider>,
  document.getElementById('root')
)

```
###:six: common.cssとreset.cssを作成する
```shell-session
$ touch src/css/common.css
```
```shell-session:src/css/reset.css
現場で決められている場合はそれを
決められていない場合は、ネットから拾ってきたものをコピペ
```

###:seven: reducerを統括するファイルを作成する
```js:src/reducers/index.js
import { combineReducers } from 'redux'

const reducer = combineReducers({
});

export default reducer;
```


##ページ追加例
[コードで理解するRedux(React使用) ](https://qiita.com/enshi/items/19b1924b72f8c2ffd1eb)
例として上記で作った
数値を入力してボタンを押すと税込みの金額が出るページを作ります。

###:one: action作成
ファイルが異なっていてもtypeが同じcreateActionが複数存在すると両方のreducerが走ってしまうので、
「ADD_TAX_」のようにプレフィックスとしてドメインを付けるべきだと思う。（良い方法があれば教えてください。。）

```js:src/actions/addTax.js
import { createAction } from 'redux-actions';

//金額変更
export let changeMoney = createAction('ADD_TAX_CHANGE_MONEY');

//税込み計算
export let calc = createAction('ADD_TAX_CALC');
```
###:two: reducer作成
```js:src/reducers/addTax.js
import { handleActions } from 'redux-actions';

import * as AddTax from '../actions/addTax';


const initialState = {
	money: '',
	includeTax: '',
};

const Reducer = handleActions({
	//金額変更
	[AddTax.changeMoney]: (state, action) => (
		Object.assign({}, state, {
			money: action.payload,
		})
	),
	//税込み計算
	[AddTax.calc]: (state, action) => (
		Object.assign({}, state, {
			includeTax: parseInt(action.payload * 1.08, 10),
		})
	),
}, initialState);

export default Reducer;
```

reducer/index.jsで追加したreducerを読み込む

```js:src/reducers/index.js
import { combineReducers } from 'redux'

import AddTax from './addTax'　←　追加

const reducer = combineReducers({
	AddTax,　←　追加
});

export default reducer;
```

###:four: component追加
Container Component

```js:src/components/AddTax/AddtaxContainer.js
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';

import Component from './AddTax';
import * as Actions from './../../actions/addTax';


const mapStateToProps = (state) => (state.AddTax);

const mapDispatchToProps = (dispatch) => (bindActionCreators(Actions, dispatch));


const Container = connect(
  mapStateToProps,
  mapDispatchToProps,
)(Component);

export default Container;
```

Presentaional Component

```js:src/components/AddTax/AddTax.js
import React from 'react';

//Material-Ui
import TextField from 'material-ui/TextField';
import RaisedButton from 'material-ui/RaisedButton';

export default class Component extends React.Component {

	//金額変更
	changeMoney(event, value){
		this.props.changeMoney(value);
	}

	//税込み計算
	calc(){
		this.props.calc(this.props.money);
	}


	render() {
		return (
			<div>
				<TextField
					id="money"
					value={this.props.money}
					onChange={this.changeMoney.bind(this)}
				/>
				<RaisedButton
					label="計算"
					onTouchTap={this.calc.bind(this)}
				/>
				{this.props.includeTax}
			</div>
		);
	}
};
```

###:five: ルーティングファイル追加
\<NavLink\>コンポーネントはtoで指定したものと現在のURLパスが一致した時に、activeClassNameで指定したものがclassに追加される。
現在開いているページのボタンの色を変えたいときなどに使用する。

```js:src/components/App/App.js
import React from 'react';
import { NavLink, Route } from 'react-router-dom'

//Material-Ui
import Paper from 'material-ui/Paper';
import * as Colors from 'material-ui/styles/colors';

import AddTax from '../AddTax/AddTaxContainer'


export default class Component extends React.Component {

	render() {
		return (
			<div>
				<Paper
					style={{
						backgroundColor: Colors.cyan400,
						padding: "2px",
					}}
				>
					<NavLink to='/addtax' className="menuButton" activeClassName="activeMenuButton">
						消費税計算
					</NavLink>
				</Paper>

				<Route exact path='/' component={AddTax} />	{/* /のときAddTaxを読み込む */}
				<Route path='/addtax' component={AddTax} />	{/* /addtaxのときAddTaxを読み込む */}
			</div>
		);
	}
};
```
###:six: common.cssにグローバルメニューのボタンのスタイルを追記する
```css:src/css/common.css
.menuButton {
	color: black;
	font-size: 14px;
	text-decoration: none;
	background-color: paleturquoise;
	margin: 5px;
	padding: 12px;
	border-radius: 2px;
	display: inline-block;
	vertical-align: middle;
}

.menuButton:hover {
	background-color: lightgoldenrodyellow;	/* ボタンをホバーした時背景色を変更する */
}

.activeMenuButton {
	background-color: lightgoldenrodyellow;	/* アクティブのときボタンの背景色を変更する */
}
```

#サーバーサイド
##新規PJ作成
###:one: nodejsのテンプレートでプロジェクト作成
```shell-session
$ mkdir lambda_hoge
$ cd lambda_hoge
$ sls create -t aws-nodejs
$ ls
handler.js  serverless.yml
$ rm handler.js
```
作られるファイルの説明

| ファイル | 説明 |
|:-:|:--|
| handler.js | lambda関数のサンプル（消してもOK）  |
| serverless.yml | デプロイの設定ファイル  |


###:two: 必要パッケージインストール
参考：[個人的React周りベストプラクティス④ - Lamdaパッケージ編](https://qiita.com/enshi/items/68ad5cbe6010b2d12ce9)

```shell-session
$ npm init
出て来る質問はすべて何も入力せずEnter

$ npm install serverless-offline
$ npm install lambda-wrapper　←　不要なら入れなくても良い
$ npm install serverless-plugin-split-stacks　←　不要なら入れなくても良い
```

###:three: serverless.yml書き換え
インデントはタブではなくスペースでなければならない

```yaml:serverless.yml
service: lambda_hoge

package:
  include:
    - ../lib/**
    - ../commonConstant.js

plugins:
  - serverless-offline
  - serverless-plugin-split-stacks　←　不要なら入れなくても良い

custom:
  serverless-offline:
    host: 0.0.0.0
    port: 50000

provider:
  name: aws
  runtime: nodejs6.10
  region: ap-northeast-1
  memorySize: 128
  timeout: 30

functions:
```
それぞれの内容解説

| 項目 | 説明 |
|:--|:--|
| service | アプリケーション名 |
| package | パッケージに含めるもの、含めないものを設定する |
| 　include | serverless.ymlよりも上位の階層にあるファイルを使えるようにする<br />デプロイした時にserverless.ymlと同階層になるのでrequire時に注意（process.env.IS_OFFLINE === 'true'で判定しローカルの時とデプロイした時で分ける） |
| plugins | 追加パッケージ |
| custom | カスタム設定 |
| 　serverless-offline | serverless-offlineのカスタム設定 |
| 　　host | serverless-offlineのホスト設定 |
| 　　port | 関数をローカルで実行した時のポート、開いているポートをPJのメンバーで合わせる<br />デフォルトだと3000になってしまいフロント側とかぶる |
| provider | アプリケーションのデプロイ先の設定 |
| 　name | デプロイ先。”aws”固定 |
| 　runtime | 言語 |
| 　region | リージョン 東京の場合はap-northeast-1 |
| 　memorySize | デフォルトのメモリサイズ |
| 　timeout | デフォルトのタイムアウト時間 |
| functions | 関数の設定 |

##関数追加

###:one: 関数作成
```js:testFunction.js
'use strict';

module.exports.handler = (event, context, callback) => {

	let response = {
		statusCode: 200,
		headers: {
			"Access-Control-Allow-Origin": "*",
			"Access-Control-Allow-Headers": "Content-Type,X-Amz-Date,Authorization,X-Api-Key,X-Amz-Security-Token",
		},
		body: 'lambdaからお送りします',
	};

	callback(null,　response);
}
```

###:two: serverless.yml追記
serverless.ymlの一番下に以下を追記する

```yaml:serverless.yml
functions:
   testFunction:　←　lambdaに登録される関数名
    handler: testFunction.handler　←　実行する関数（testFunction.jsのhandlerという関数）
    events:
      - http: post testFunction　←　APIGatewayの実行パス（https://~~~~~~~~/testFunctionにPOSTでアクセスすると実行される）
```

#次の記事
[個人的React周りベストプラクティス⑦ - 運用編](https://qiita.com/enshi/items/68b0a66d2fcfba99c9a8)