lambda
redux
serverless

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

前の記事

前回:個人的React周りベストプラクティス⑤ - Reduxディレクトリ構成編
一番始め:個人的React周りベストプラクティス① - 構成編

フロントサイド

新規PJ作成

:one: create-react-appで基本的なReactアプリを作成する

$ create-react-app redux_hoge

redux_hogeというディレクトリの中に色々と作られる

:two: 必要パッケージ、ミドルウェアインストール

参考:個人的React周りベストプラクティス③ - Reduxパッケージ・ミドルウェア編

$ 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内の全ファイル削除

$ rm -f src/*

:four: 空のディレクトリを作成する

参考:個人的React周りベストプラクティス⑤ - Reduxディレクトリ構成編

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

:five: index.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を作成する

$ touch src/css/common.css
src/css/reset.css
現場で決められている場合はそれを
決められていない場合は、ネットから拾ってきたものをコピペ

:seven: reducerを統括するファイルを作成する

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

const reducer = combineReducers({
});

export default reducer;

ページ追加例

コードで理解するRedux(React使用)
例として上記で作った
数値を入力してボタンを押すと税込みの金額が出るページを作ります。

:one: action作成

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

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作成

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を読み込む

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

import AddTax from './addTax' ← 追加

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

export default reducer;

:four: component追加

Container Component

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

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に追加される。
現在開いているページのボタンの色を変えたいときなどに使用する。

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にグローバルメニューのボタンのスタイルを追記する

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のテンプレートでプロジェクト作成

$ 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パッケージ編

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

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

:three: serverless.yml書き換え

インデントはタブではなくスペースでなければならない

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

関数追加

:one: 関数作成

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の一番下に以下を追記する

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

次の記事

個人的React周りベストプラクティス⑦ - 運用編