react-router を使って React でルーティング

SPA を作る上で実装したいのがルーティング。React を使ったアプリでのルーティング実装には react-router を使う。 今回からインストールは npm で。

npm install --save react-router

react-router を使って Master-Detail の簡単なサンプルを書いてみた。 react-router が提供する Router や Route は React コンポーネントなので、 DOM を組み立てる感覚でルーティングを定義できて面白い。

var React = require("react");
var ReactDOM = require("react-dom");
var ReactRouter = require("react-router");
var Router = ReactRouter.Router;
var Link = ReactRouter.Link;
var Route = ReactRouter.Route;
var IndexRoute = ReactRouter.IndexRoute;

// ユーザー情報を格納したストア
var initialUsers = [
  { 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 }
];

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({
  getInitialState: function() {
    return {
      users: initialUsers
    };
  },

  getUsers: function() {
    return this.state.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 = initialUsers[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}
        </div>
      </div>
    );
  }
});

// Router や Route も React コンポーネント
ReactDOM.render((
  <Router>
    <Route path="/" component={MyApp}>
      <IndexRoute component={Users}/>
      <Route path="users" component={Users}/>
      <Route path="users/:id" component={UserDetail}/>
      <Route path="about" component={About}/>
    </Route>
  </Router>
  ),
  document.getElementById("content")
);

ルーティングにマッチしたコンポーネントは props.children、 パスに埋め込まれたパラメータは props.params に格納される。

Link でクエリパラメータを渡せるけど、id やフラグといった、ちょっとした情報を渡す程度にとどめて置くのが無難。 すべての情報を渡すのは適してない。 そのため、id をもとにストアから詳細な情報を取り出す必要があった。

今回も「コンテナコンポーネントに状態をもたせて子コンポーネントはステートレスにする」設計をなんとか実装したかったが、 上手いやり方が思いつかず断念。react-router に精通すれば可能なんだろうか。 今後の課題だな。