React + Redux でもルーティング

react-router でルーティングを試してみたけど、 実際のアプリは React + Redux で開発する予定。 その際、react-router のみではなく redux-router も一緒に使う。

redux-router は react-router の Redux バインディングルーターの状態を Redux のストアで管理できるようになる。

github.com

redux-router は npm でインストール。 redux-router が依存している history も併せてインストールしておく。

npm install --save history redux-router

react-router のサンプルを Redux と redux-router を使って書き変えてみた。

var React = require("react");
var ReactDOM = require("react-dom");

var Link = require("react-router").Link;
var Route = require("react-router").Route;
var IndexRoute = require("react-router").IndexRoute;

var combineReducers = require("redux").combineReducers;
var createStore = require("redux").createStore;
var compose = require("redux").compose;

var Provider = require("react-redux").Provider;
var connect = require("react-redux").connect;

var reduxReactRouter = require("redux-router").reduxReactRouter;
var routerStateReducer = require("redux-router").routerStateReducer;
var createElement = require("redux-router").createElement;
var ReduxRouter = require("redux-router").ReduxRouter;

// HTML だけで完結したいので hash history を使う
var createHashHistory = require("history").createHashHistory;

// ユーザー一覧のユーザーを表示するコンポーネント
var User = React.createClass({
  render: function() {
    return (
        <tr>
          <td>
            <Link to={"/users/" + this.props.user.id} query={{name: this.props.user.name}}>
              {this.props.user.name}
            </Link>
          </td>
        </tr>
    );
  }
});

// ユーザー一覧を表示するコンポーネント
var Users = React.createClass({
  getUsers: function() {
    return this.props.users.map(function(u) {
      return <User user={u} />;
    });
  },

  render: function() {
    return (
      <div className="users">
        <h1>ユーザー一覧</h1>
        <table>
          <thead>
            <tr>
              <th>名前</th>
            </tr>
          </thead>
          <tbody>
            {this.getUsers()}
          </tbody>
        </table>
      </div>
    );
  }
});

// ユーザー詳細を表示するコンポーネント
var UserDetail = React.createClass({
  render: function() {
    // パスに埋め込まれたパラメータは params から取得できる
    var userId = this.props.params.id;

    // ユーザーを取得
    var user = this.props.users[userId - 1];

    // 名前はクエリパラメータで受け取ったものを表示してみる
    return (
      <div className="user-detail">
        <h1>ユーザー詳細</h1>
        <dl>
          <dt>ID</dt>
          <dd>{user.id}</dd>
          <dt>名前</dt>
          <dd>{this.props.location.query.name}</dd>
          <dt>年齢</dt>
          <dd>{user.age}</dd>
        </dl>
      </div>
    );
  }
});

var About = React.createClass({
  render: function() {
    return (
      <div className="about">
        <h1>このサンプルについて</h1>
        <p>react-router のサンプルです。</p>
      </div>
    );
  }
});

// コンテナコンポーネント
var _MyApp = React.createClass({
  render: function() {
    return (
      <div>
        <ul>
          <li><Link to="/users">ユーザー一覧</Link></li>
          <li><Link to="/about">このサンプルについて</Link></li>
        </ul>
        <div>
          {this.props.children && React.cloneElement(this.props.children, {
            users: this.props.users
          })}
        </div>
      </div>
    );
  }
});

// connect でラップして、
// ストアから取り出したユーザー一覧を MyApp の props に渡せるようにする
var MyApp = connect(function(state) {
  return {
    users: state.users
  };
})(_MyApp);

// ユーザー一覧を操作する Reducer
// といってもユーザー一覧の初期データを返すだけ
function usersReducer(state, action) {
  return state || [
    { id: 1, name: "香川", age: 26 },
    { id: 2, name: "本田", age: 29 },
    { id: 3, name: "長友", age: 29 },
    { id: 4, name: "岡崎", age: 29 },
    { id: 5, name: "内田", age: 28 },
    { id: 6, name: "清武", age: 25 }
  ];
}

// ストアに渡す Reducer を作成
var reducer = combineReducers({
  router: routerStateReducer,
  users: usersReducer
});

// 唯一のストアを作成
// 履歴を扱えるように reduxReactRouter ミドルウェアを適用
var store = compose(
  reduxReactRouter({ "createHistory": createHashHistory })
)(createStore)(reducer);

// ReduxRouter を使ってルーティングを定義
ReactDOM.render((
  <Provider store={store}>
    <ReduxRouter>
      <Route path="/" component={MyApp}>
        <IndexRoute component={Users} />
        <Route path="users" component={Users} />
        <Route path="users/:id" component={UserDetail} />
        <Route path="about" component={About}/>
      </Route>
    </ReduxRouter>
  </Provider>
  ),
  document.getElementById("content")
);

前回、遷移先のコンポーネントに props を渡す方法を習得したので、 状態を Redux のストアに一元管理できて良い感じ。