前回作成したExpressサーバからイベントデータを取得して、Reactで一覧表示させてみます。
www.case-k.jp
バックエンドは前回作成したExpressサーバを使いフロントエンドをReactで作ります。
React:localhost:3000, Express:localhost:3001
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 ( //イベント一覧 ) } }
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)
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ポートを確認してみます。
daveceddia.com
dev.to