case-kの備忘録

日々の備忘録です。データ分析とか基盤系に興味あります。

React-ReduxとExpressでイベント一覧を取得表示する

前回作成したExpressサーバからイベントデータを取得して、Reactで一覧表示させてみます。
f:id:casekblog:20200105193428p:plain
www.case-k.jp
バックエンドは前回作成したExpressサーバを使いフロントエンドをReactで作ります。
React:localhost:3000, Express:localhost:3001
f:id:casekblog:20200105194103j:plain:w300

Code

全体のコードはこちらになります。
github.com

セットアップ

環境設定します。
※ 前回の記事で追加したパッケージを前提にしてます。

# react-backend配下でプロジェクト作成
npm install -g create-react-app
create-react-app client
# パッケージを追加
yarn add redux
yarn add react-redux
yarn add axios
yarn add redux-thunk
yarn add loader

package.jsonに"proxy": "http://localhost:3001"を追加

  "proxy": "http://localhost:3001",

Reactの概要

ReactはUIの部品を作るためのライブラリです。特徴としてはJavaScript 内に HTMLを記載できる技法(JSX)や仮想 DOMによりレンダリングが早いことがあげられます。

Componentとは

ReactではComponentを組み合わせて画面を生成します。今回はイベント一覧を返すComponentを作成します。

class EventsIndex extends Component {
  ・・・
  render() {
    return (
      //イベント一覧
    )
  }
}

qiita.com

React-Reduxとは

React-ReduxはどのComponentからでも最新の状態を取得するための仕組みです。React-Reduxを使うことでStoreと呼ばれる箇所で状態を一元管理することが可能になり、各Componentからも最新の状態を取得、更新できるようになります。React-Reduxで状態を更新するにはComponentからActionとStateを渡す必要があります。
React-Reduxについては下記の記事がわかりやすかったです。
qiita.com

Actionとは

Actionとはアプリケーションの中で何がおきたかを定義するものです。JavaScriptのオブジェクトで定義されます。例えば数値を増やすActionを定義したい場合以下のようにして定義します。このようにActionを返す関数をReduxではActon Creatorと呼ばれます。定義したActon CreatorはComponent側で利用されます。

export const INCREMENT = 'INCREMENT'

export const increment = () => ({
	type: INCREMENT
})

StateとPropsについて

StateもPropsどちらもComponentの状態を表します。StateはComponent単体で保持してる状態で、Propsは親がComponentを保持してる状態です。ComponentがStoreから何か情報を受け取る場合、それはPropsを通じて渡されます。

const props = this.props
console.log(props)

f:id:casekblog:20200105230145p:plain:w500
qiita.com

Storeについて

状態は基本的にこのStoreで管理されます。ComponentはこのStoreを参照し最新の状態を取得します。

# src/index.js
const store = createStore(reducer, applyMiddleware(thunk))

実践編

実際にイベント一覧を表示させるアプリを作ってみます。

Componentを定義

まずは一覧を表示させるComponentを作ります。ComponentではAPIを呼ぶ処理、Storeの状態(イベント情報)を更新する処理、取得したイベントを一覧で表示する処理を定義します。一覧表示については下記のようにPropsから状態を取得しリストで表示します。

# src/components/event_index.js
class EventsIndex extends Component {
  ・・・
  displayEventsList() {
    return _.map(this.props.events, event => (
      <tr key={event.id}>
        <td>{event.id}</td>
        <td>{event.title}</td>
        <td>{event.body}</td>
      </tr>
    ))
  }

  render() {
    const props = this.props
    console.log(this.props);
    return (
      <table>
        <thead>
          <tr>
           <th>ID</th>
           <th>Title</th>
           <th>Body</th>
          </tr>
        </thead>
        <tbody>
        {this.displayEventsList()}
        </tbody>
      </table>
    )
  }
}

Actionの実行はcomponentDidMountメソッドに記載します。このメソッドはComponentがマウントされたタイミングで実行されます。ここに外部のAPIサーバに対しイベントを取得する処理を記載します。イベントを取得する処理(readEvents)はActionに記載するのがReact-Reduxの流儀のようです。

# src/components/event_index.js
componentDidMount() {
  this.props.readEvents()
}

ComponentからStateとActionを渡すためには下記のように定義します。
State(state.events)とAction(readEvents)を渡し新しい状態を作り出します。

# src/components/event_index.js
class EventsIndex extends Component {
・・・
}
const mapStateToProps = state => ({events: state.events})
const mapDispatchToProps = ({ readEvents}) 
export default connect(mapStateToProps, mapDispatchToProps)(EventsIndex)

次にReducerとActionを定義して行きます。

Actionを定義

次にアクションを定義します。アクションタイプ(READ_EVENTS)とExpressサーバからデータを取得する関数を定義します。外部リクエストを投げるにHTTPクライアントを行うためにaxiosを使います。

# src/actions/index.js
import axios from 'axios'
export const READ_EVENTS = 'READ_EVENTS'

export const readEvents = () => async dispatch => {
	const response = await axios.get('/events/EventsDataList')
        console.log(response);
	dispatch({ type: READ_EVENTS, response })
}

外部にリクエストを投げる関数を定義することができました。ですがActionには純粋なオブジェクトのみ返す必要があり、関数を返すことはできません。
Actionの代わりに関数を返すようにするためにはredux-thunkを使う必要があります。
www.npmjs.com
reducersのindex.jsにapplyMiddlewareとredux-thunkをインポートしておきます。さらにcreateStoreで定義することでreduxのstoreの中に組み込むことが可能になります。

# src/index.js
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk'
const store = createStore(reducer, applyMiddleware(thunk))

ここで定義したアクションをComponentで定義しています。

class EventsIndex extends Component {
 ・・・
     componentDidMount() {
       this.props.readEvents()
    }
    ・・・
}
・・・
const mapDispatchToProps = ({ readEvents}) 

Reducerを定義

ReducerはActionに応じて条件分岐させ返り値を変更します。タイプに応じて処理が分岐するのでswitch文を使います。

# src/reducers/events.js
import _ from 'lodash'
import {READ_EVENTS} from '../actions'

export default (events=[], action) => {
	switch (action.type) {
	  case READ_EVENTS:
	    return _.mapKeys(action.response.data, 'id')
	  default:
	    return events
	}
}

Reducerで定義したevents.jsなどは全てindex.jsでまとめて参照するようにします。

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

export default combineReducers({ events })

先ほどComponentでPropsに渡したStateはReducerのindex.jsで定義したものに合わせる必要があります。

# src/components/event_index.js
const mapStateToProps = state => ({events: state.events})

ここまで定義することでComponentからStoreの情報を参照できるようになりました。更新された状態を参照し、イベント一覧を表示します。localhost:3000ポートを確認してみます。
f:id:casekblog:20200105193428p:plain
daveceddia.com
dev.to