React を 触ってみた (5) – 非同期処理 と Router
今回は サイドメニュー に WordPress の Rest API から取得した カテゴリー と タグ を 表示させます
前回の記事
React を 触ってみた (4) – Redux
https://tekuaru.jack-russell.jp/2018/05/01/1750/
WordPress の Rest API から データを取得 するには 非同期処理が 必要になります
その際 必要になるのが redux-thunk という パッケージ
また カテゴリ や タグ を クリックした際 URL も 変えたいと思うので
その際 必要になるのが react-router-dom という パッケージになります
redux-thunk と react-router-dom の インストール
(Dir : /var/www/html/)
今回も 作成に必要な パッケージを インストールします
npm install --save-dev redux-thunk react-router-dom
各々のバージョンは 以下のようになりました
"react-router-dom": "^4.2.2", "redux-thunk": "^2.2.0",
ファイル作成
わかりやすいように src 以下に component-parts ディレクトリを 作成します
/var/www/html/
-> src
-> component-parts
その中に
list-categories.jsx
list-tags.jsx
の ファイルを 作成します
ファイル名から 分かる通り 各々 カテゴリー と タグ を リスト表示させるものです
早速ソースを見てもらいましょう
/src/component-parts/list-categories.jsx
import React from 'react' import { Link } from 'react-router-dom' import PropTypes from 'prop-types' import { withStyles } from 'material-ui/styles' import List, { ListSubheader, ListItem, ListItemText } from 'material-ui/List' import { connect } from 'react-redux' import { mapStateToProps, mapDispatchToProps } from '../redux/containers' const styles = theme => ({ drawerListSubheader: { backgroundColor: theme.palette.background.paper, }, }) class ListCategories extends React.Component { constructor (props) { super(props) } componentWillMount() { // componentDidMount() { var categoriesURL = this.props.API.default + this.props.API.namespace + 'categories/' + '?' + 'per_page=100' this.props.handleLoadCategories(categoriesURL) } render(){ const { classes } = this.props return( <div> <List> <ListSubheader className={classes.drawerListSubheader} component="div">Categories</ListSubheader> {this.props.categories.map((item) => { return ( <ListItem button key={item.id} component={Link} to={`/categories/${item.id}`}> <ListItemText primary={item.name} /> </ListItem> ) })} </List> </div> ) } } ListCategories.propTypes = { classes: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, } export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles, { withTheme: true })(ListCategories))
/src/component-parts/list-tags.jsx
import React from 'react' import { Link } from 'react-router-dom' import PropTypes from 'prop-types' import { withStyles } from 'material-ui/styles' import List, { ListSubheader, ListItem, ListItemText } from 'material-ui/List' import { connect } from 'react-redux' import { mapStateToProps, mapDispatchToProps } from '../redux/containers' const styles = theme => ({ drawerListSubheader: { backgroundColor: theme.palette.background.paper, }, }) class ListTags extends React.Component { constructor (props) { super(props) } componentWillMount() { // componentDidMount() { var tagsURL = this.props.API.default + this.props.API.namespace + 'tags/' + '?' + 'per_page=100' this.props.handleLoadTags(tagsURL) } render(){ const { classes } = this.props return( <div> <List> <ListSubheader className={classes.drawerListSubheader} component="div">Tags</ListSubheader> {this.props.tags.map((item) => { return ( <ListItem button key={item.id} component={Link} to={`/tags/${item.id}`}> <ListItemText primary={item.name} /> </ListItem> ) })} </List> </div> ) } } ListTags.propTypes = { classes: PropTypes.object.isRequired, theme: PropTypes.object.isRequired, } export default connect(mapStateToProps, mapDispatchToProps)(withStyles(styles, { withTheme: true })(ListTags))
もともと main.jsx に あった
<List> <ListSubheader className={classes.drawerListSubheader} component="div">First List</ListSubheader> <ListItem button> <ListItemText primary="First List Button" /> </ListItem> </List>
<List> <ListSubheader className={classes.drawerListSubheader} component="div">Second List</ListSubheader> <ListItem button> <ListItemText primary="Second List Button" /> </ListItem> </List>
の 2つを 切り出した形に なっています
componentWillMount & componentDidMount とは
componentWillMount() { /* 処理 */ } componentDidMount() { /* 処理 */ }
今回 増えた componentWillMount や componentDidMount は マウント(呼び出し)されるときに 1度だけ 実行されます
componentWillMount は render 前 に 処理 されて
componentDidMount は render 後 に 処理 されます
今回は カテゴリー や タグ を 取得するための 関数を 呼び出しています
component={Link} to={`/hoge/${huga.id}`} とは
こちらが クリックした際 URL も 変えるための 処理になります
Link という コンポーネントを 読み込み どのようなURL に 飛ぶのかは to で 指定しています
今回は htaccess による リダイレクト処理をしていないので 全部 ハッシュ で 渡していて 見た目だけの URLを 変えています
Redux の 処理追記
今回 handleLoadCategories と handleLoadTags という 新しい 関数を 追加したので その処理を 書いていきます
/src/redux/containers.jsx
import React from 'react' import { connect } from 'react-redux' import Main from '../component/main' import { toggleDrawer, loadCategories, loadTags } from './actions' export function mapStateToProps(state) { return state } export function mapDispatchToProps(dispatch) { return { handleToggleDrawer: () => { dispatch( toggleDrawer() ) }, handleLoadCategories: (value) => { dispatch( loadCategories(value) ) }, handleLoadTags: (value) => { dispatch( loadTags(value) ) }, } } export default connect(mapStateToProps, mapDispatchToProps)(Main)
list-categories.jsx と list-tags.jsx から mapStateToProps と mapDispatchToProps を 呼び出すようになったため export を 追加
mapDispatchToProps に
handleLoadCategories: (value) => { dispatch( loadCategories(value) ) }, と
handleLoadTags: (value) => { dispatch( loadTags(value) ) }, を 追加
import に loadCategories と loadTags を 追加
が 今回の 変更点です
/src/redux/actions.jsx : 追記
export function loadCategories( value ) { return function(dispatch) { fetch(value) .then(response => response.json()) .then(result => dispatch(setCategories(result))) .catch(error => console.log(error)) // end fetch } } export function setCategories( value ) { return { type: 'SET-CATEGORIES', value, } } export function loadTags( value ) { return function(dispatch) { fetch(value) .then(response => response.json()) .then(result => dispatch(setTags(result))) .catch(error => console.log(error)) // end fetch } } export function setTags( value ) { return { type: 'SET-TAGS', value, }
loadCategories や loadTags は fetch 処理を通して setCategories や setTags に 渡すようになっています
fetch の 結果が帰ってきたら アクションを起こすように 2つに分けて 記述しているのが ポイントになります
/src/redux/reducers.jsx : function のみ
export default function reducer(state = initialState, action) { switch(action.type) { case 'TOGGLE-DRAWER': console.log( 'mobileOpen : ' + !state.mobileOpen ) return Object.assign({}, state, { mobileOpen: !state.mobileOpen, }) case 'SET-CATEGORIES': console.log( 'categories :' ) console.log( action.value ) return Object.assign({}, state, { categories: action.value, }) case 'SET-TAGS': console.log( 'tags :' ) console.log( action.value ) return Object.assign({}, state, { tags: action.value, }) default: return state } }
SET-CATEGORIES や SET-TAGS 記述方法は TOGGLE-DRAWER と 大体おなじになります
ListCategories と ListTags に 差し替え
list-categories.jsx と list-tags.jsx を 作ったので main.jsx から 呼び出して 表示するようにします
/src/component/main.jsx : import部
import React from 'react' import PropTypes from 'prop-types' import { withStyles } from 'material-ui/styles' import AppBar from 'material-ui/AppBar' import Toolbar from 'material-ui/Toolbar' import IconButton from 'material-ui/IconButton' import MenuIcon from '@material-ui/icons/Menu' import Hidden from 'material-ui/Hidden' import Drawer from 'material-ui/Drawer' import Divider from 'material-ui/Divider' import Typography from 'material-ui/Typography' import ListCategories from '../component-parts/list-categories' import ListTags from '../component-parts/list-tags'
import List, { ListSubheader, ListItem, ListItemText } from ‘material-ui/List’ は
list-categories.jsx と list-tags.jsx 側に 持つので 必要なくなりました
その代わり
import ListCategories from ‘../component-parts/list-categories’
import ListTags from ‘../component-parts/list-tags’
を インポートしています
/src/component/main.jsx : const drawer 部
const drawer = ( <div> <ListCategories /> <Divider /> <ListTags /> </div> )
ListCategories と ListTags に 単純に差し替えた形です
redux-thunk と react-router-dom を 連携
このままでは 非同期処理が 正しく処理されませんし
list-categories.jsx と list-tags.jsx に Link to を 設定しましたが 動かないので 連携を行っていきます
/src/index.jsx
import React from 'react' import ReactDOM from 'react-dom' import { HashRouter, Route } from 'react-router-dom' import { createStore, applyMiddleware } from 'redux' import { Provider } from 'react-redux' import thunk from 'redux-thunk' import Containers from './redux/containers' import Reducers from './redux/reducers' const store = createStore(Reducers, applyMiddleware(thunk)) ReactDOM.render( <Provider store={store}> <HashRouter basename='/'> <div> <Route component={Containers} /> </div> </HashRouter > </Provider>, document.getElementById('root') )
ココの記述は 連携を行うたびに ガラッと変わっている気がします…
今回は
import { HashRouter, Route } from ‘react-router-dom’
import { applyMiddleware } from ‘redux’
import thunk from ‘redux-thunk’
が 新規で import されています
また store に applyMiddleware(thunk) が 追記され 非同期処理が 行えるようになっています
<Containers />
だけだったのが
<HashRouter basename='/react'> <div> <Route exact path='/' component={Containers} /> </div> </HashRouter >
と なっており HashRouter や Route を 通るようになりました
ページを確認
First React Project
http://react/
サイドメニューが 読み込まれているか 確認してみましょう
( コンパイルを 忘れずにね )
まとめ
今回は 単純な ソースの紹介になってしまいました…
我ながら 読みにくい 稚拙な文章だなって思います…
これからも ソースの紹介になってしまうかと思いますが ご購読お願い致します
現状 カテゴリ や タグ は 100件までしか 取得していないので
次回は それ以上件数も 取得するように 調整 や ソースコードの 整理を 行おうと考えています