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/)
今回も 作成に必要な パッケージを インストールします
1 | npm install --save-dev redux-thunk react-router-dom |
各々のバージョンは 以下のようになりました
1 2 | "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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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 に あった
1 2 3 4 5 6 | <List> <ListSubheader className={classes.drawerListSubheader} component= "div" >First List</ListSubheader> <ListItem button> <ListItemText primary= "First List Button" /> </ListItem> </List> |
1 2 3 4 5 6 | <List> <ListSubheader className={classes.drawerListSubheader} component= "div" >Second List</ListSubheader> <ListItem button> <ListItemText primary= "Second List Button" /> </ListItem> </List> |
の 2つを 切り出した形に なっています
componentWillMount & componentDidMount とは
1 2 3 4 5 6 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 | 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 : 追記
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 のみ
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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部
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | 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 部
1 2 3 4 5 6 7 | 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
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | 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) が 追記され 非同期処理が 行えるようになっています
1 | <Containers /> |
だけだったのが
1 2 3 4 5 | <HashRouter basename= '/react' > <div> <Route exact path= '/' component={Containers} /> </div> </HashRouter > |
と なっており HashRouter や Route を 通るようになりました
ページを確認
First React Project
http://react/
サイドメニューが 読み込まれているか 確認してみましょう
( コンパイルを 忘れずにね )
まとめ
今回は 単純な ソースの紹介になってしまいました…
我ながら 読みにくい 稚拙な文章だなって思います…
これからも ソースの紹介になってしまうかと思いますが ご購読お願い致します
現状 カテゴリ や タグ は 100件までしか 取得していないので
次回は それ以上件数も 取得するように 調整 や ソースコードの 整理を 行おうと考えています