Redux routerでページ遷移を実現する
この記事はQiita React.js Advent Calendar 2015の18日目に投稿させて頂いた記事です。
モチベーション
reduxのページ切り替えにreact routerを導入したい
成果物
redux-routing-example/containers at master · takanabe/redux-routing-example · GitHubにredux-routerを使ったサンプルプロジェクトを置いておく。
redux-routerとは
redxu-routerはreact-routerのAPIをReduxから利用出来るようにするもの。
Reduxではステートを1つのStoreで管理する規則があるが、redux-routerを使うことで、RouterもStoreで管理できるようになる。
ルーティングの例
ルーティングの設定はreact-routerと同じ。例えば、以下のようなルーティングの設定がされているとする。
<Route path="/" component={Parent} >
<IndexRoute component={ChildA} />
<Route path="child_a" component={ChildA} />
<Route path="/child_b/:id" component={ChildB} >
<Route path="gchild_a" component={GrandChildA} />
<Route path="gchild_b" component={GrandChildB} />
</Route>
<Route path="*" component={NotFound} />
</Route>
この時、アクセスするパスでRouterはレンダリングするComponentを決定する。
”/“にアクセスした場合
- Parent Componentをレンダリング対象にする
- 子階層を見に行き、pathにマッチしているComponentを探す
- pathにマッチするものがないのでIndex Routeに設定されているChildA Componentをレンダリング対象にする
- ChildA Componentに子階層がないためルーティングを終了する
”/child_aにアクセスした場合
- Parent Componentをレンダリング対象にする
- 子階層を見に行き、pathにマッチしているComponentを探す
- pathがChildAのpathとマッチしているのでChildA Componentをレンダリング対象にする
- ChildA Componentに子階層がないためルーティングを終了する
”/child_a/xxxx”にアクセスした場合
- Parent Componentをレンダリング対象にする
- 子階層を見に行き、pathにマッチしているComponentを探す
- pathが
*
にマッチするのでNotFound Componentをレンダリング対象にする - NotFound Componentに子階層がないためルーティングを終了する
”/child_b/xxxx”にアクセスした場合
- Parent Componentをレンダリング対象にする
- 子階層を見に行き、pathにマッチしているComponentを探す
- pathがChildBのpathとマッチしているのでChildB Componentをレンダリング対象にする
- 子階層を見に行き、pathにマッチしているComponentを探す
- pathにマッチするものがなく、Default Routeに設定されているGrandChildAをレンダリング対象にする
”/child_b/xxxx/gchild_b”にアクセスした場合
- Parent Componentをレンダリング対象にする
- 子階層を見に行き、pathにマッチしているComponentを探す
- pathがChild-BのpathとマッチしているのでChild-B Componentをレンダリング対象にする
- 子階層を見に行き、pathにマッチしているComponentを探す
- pathにマッチしているのでGrandChild-Bをレンダリング対象にする
ルーティングを利用してComponentを入れ子にする
Componentを入れ子にしてパスによってレンダリングする子Componentを定義することっもできる。
import React from 'react';
import { Route } from 'react-router';
import App from '../containers/App';
import Search from '../containers/Search';
import Register from '../containers/Register';
import Usage from '../containers/Usage';
import NotFound from '../containers/NotFound.jsx';
export default (
<Route path="/" component={App} >
<IndexRoute component={Register} />
<Route path="registration" component={Register} />
<Route path="search" component={Search} />
<Route path="usage" component={Usage} />
<Route path="*" component={NotFound} />
</Route>
);
親コンポーネントでは{children}を利用する。
# App.jsx
import React, { Component, PropTypes } from "react";
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import { pushState } from 'redux-router';
import Header from '../components/Header';
import Footer from '../components/Footer';
class App extends Component {
render() {
return (
<div>
<Header />
<div>
{this.props.children}
</div>
<Footer />
</div>
);
}
}
App.propTypes = {
};
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(
mapStateToProps,
{pushState}
)(App);
実例
準備
takanabe/react-redux-material_ui-boilerplate · GitHubのboilerplateを利用してベースとなるアプリケーション環境の準備をする。
Step1 App Componentのルーティングの設定
全てのComponentを内包するApp Componentを編集する。
import React, { Component, PropTypes } from "react";
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
import Header from '../components/Header';
import Footer from '../components/Footer';
class App extends Component {
render() {
return (
<div>
<Header />
<Footer />
</div>
);
}
}
App.propTypes = {
};
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(App);
同様にHeader、Footer Componentの中身も変更しておく。
# component/Header.jsx
import React, { PropTypes, Component } from 'react';
import mui, {AppBar} from 'material-ui';
const ThemeManager = require('material-ui/lib/styles/theme-manager');
import MyRawTheme from '../src/material_ui_raw_theme_file'
class Header extends Component {
static get childContextTypes() {
return { muiTheme: React.PropTypes.object };
}
getChildContext(){
return { muiTheme: ThemeManager.getMuiTheme(MyRawTheme)};
}
render() {
return (
<header className="header">
<AppBar title="Header: Redux router example" />
</header>
);
}
}
Header.propTypes = {
};
export default Header;
# component/Footer.jsx
import React, { PropTypes, Component } from 'react';
class Footer extends Component {
render() {
return (
<footer className="footer">
<p>Footer</p>
</footer>
);
}
}
Footer.propTypes = {
};
export default Footer;
RouterのStateをRedux Storeで管理できるようにstore/configureStore.jsx
を以下のように編集する。
# 変更前
import { createStore } from 'redux';
import rootReducer from '../reducers';
export default function configureStore(initialState) {
const store = createStore(rootReducer, initialState);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers');
store.replaceReducer(nextReducer);
});
}
return store;
}
# 変更後
import routes from '../src/routes';
import { reduxReactRouter } from 'redux-router';
import createHistory from 'history/lib/createBrowserHistory';
import { createStore, compose} from 'redux';
import rootReducer from '../reducers';
const finalCrateStore = compose(
reduxReactRouter({ routes, createHistory })
)(createStore);
export default function configureStore(initialState) {
const store = finalCrateStore(rootReducer, initialState);
if (module.hot) {
// Enable Webpack hot module replacement for reducers
module.hot.accept('../reducers', () => {
const nextReducer = require('../reducers');
store.replaceReducer(nextReducer);
});
}
return store;
}
また、reducer/index.jsx
を編集してReducerにもrouterStateReducerを導入する。
import { combineReducers } from 'redux';
import { routerStateReducer as router } from 'redux-router';
const rootReducer = combineReducers({
router
});
export default rootReducer;
App Componentを表示させるためにルーティングの設定ファイルsrc/router.jsx
を作成する。
import React from 'react';
import { Route } from 'react-router';
import App from '../containers/App';
export default (
<Route path="/" component={App}>
</Route>
);
最後に、src/index.jsx
でReduxRouter Componentをレンダリングするように編集する。
import React from "react";
import ReactDOM from "react-dom";
import injectTapEventPlugin from "react-tap-event-plugin";
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import { ReduxRouter } from 'redux-router';
import configureStore from '../store/configureStore';
window.React = React;
injectTapEventPlugin();
const store = configureStore();
ReactDOM.render(
<Provider store={store}>
<ReduxRouter />
</Provider>,
document.getElementById("root")
);
Step2 Child-A,Child-B Componentのルーティングの設定
Child-A,Child-B Componentを作成する。。
# container/Child-A.jsx
import React, { Component, PropTypes } from "react";
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
class ChildA extends Component {
render() {
return (
<div>
<h1> Child A Component</h1>
</div>
);
}
}
App.propTypes = {
};
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChildA);
# container/Child-B.jsx
import React, { Component, PropTypes } from "react";
import { bindActionCreators } from 'redux';
import { connect } from 'react-redux';
class ChildB extends Component {
render() {
return (
<div>
<h1> Child B Component</h1>
</div>
);
}
}
App.propTypes = {
};
function mapStateToProps(state) {
return {
};
}
function mapDispatchToProps(dispatch) {
return {
};
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(ChildB);
Child-A,Child-B Componentを表示させるためにルーティングの設定ファイルsrc/router.jsx
を作成する。
import React from 'react';
import { Route } from 'react-router';
import Parent from '../containers/App';
import ChildA from '../containers/Child-A.jsx';
import ChildB from '../containers/Child-B.jsx';
export default (
<Route name="Parent" component={Parent} path="/">
<DefaultRoute component={ChildA} />
<Route name="child_a" component={ChildA} path="child_a" />
<Route name="child_b" component={ChildB} path="child_b/:id" />
</Route>
);
終わり。
参考
react-routerについて
- react-router/docs at master · rackt/react-router · GitHub
- 【React.js】react-routerでハマりやすいポイント - Tech Design
- React初心者のためのreact-routerの使い方 - ハッカーを目指す白Tのブログ