react-routerで簡単なSingle-page applicationを作ってみる

「Webpack+BabelでReactを使ってみる」では、WebpackとBabelを用いてReactアプリケーションを書いた。 ここでは、react-routerモジュールを使い、簡単なルーティングを含むSingle-page application(SPA)を書いてみる。

環境

Ubuntu 14.04.4 LTS 64bit版

$ uname -a
Linux vm-ubuntu64 3.19.0-25-generic #26~14.04.1-Ubuntu SMP Fri Jul 24 21:16:20 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux

$ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 14.04.4 LTS
Release:        14.04
Codename:       trusty

$ nodejs --version
v4.4.2

$ npm --version
2.15.0

ディレクトリの作成

「Webpack+BabelでReactを使ってみる」と同様に、スクリプトを置くディレクトリを作成し必要なモジュールをインストールした後、コンフィグファイルを準備する。 ここでは、追加でreact-routerをインストールする。

$ mkdir helloreact2
$ cd helloreact2/
$ npm init -y
$ npm install --save-dev webpack babel-loader babel-core babel-preset-react babel-preset-es2015
$ npm install --save react react-dom
$ npm install --save react-router

package.jsonを編集し、npmからWebpackを実行できるようにする。

{
  "name": "helloreact2",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "build": "rm -rf scripts/*.js && webpack",
    "watch": "rm -rf scripts/*.js && webpack -w"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "devDependencies": {
    "babel-core": "^6.7.6",
    "babel-loader": "^6.2.4",
    "babel-preset-es2015": "^6.6.0",
    "babel-preset-react": "^6.5.0",
    "webpack": "^1.12.15"
  },
  "dependencies": {
    "react": "^15.0.1",
    "react-dom": "^15.0.1",
    "react-router": "^2.2.2"
  }
}

次に、webpack.config.jsを作成し、Babelで./src/app.js./scripts/bundle.jsコンパイルするよう指定する。

module.exports = {
  entry: "./src/app.js",
  output: {
    path: __dirname + "/scripts",
    filename: "bundle.js"
  },
  module: {
    loaders: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        loader: "babel-loader",
        query: {
          presets: ["react", "es2015"]
        }
      }
    ]
  }
};

最後に、ソースファイルとコンパイル後のスクリプトを置くディレクトリを作成する。

$ mkdir src scripts

ルーティングするスクリプトを書いてみる

まず、コンパイルされたJavaScriptを読み込んで実行するHTMLファイルを作成する。

<!-- index.html -->
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8"/>
    <title>Hello, React!</title>
  </head>
  <body>
    <div id="content"></div>
    <script type="text/javascript" src="scripts/bundle.js"></script>
  </body>
</html>

次に、Reactとreact-routerを使い、簡単なルーティングに合わせてページ内容を出力するスクリプトを書く。

// src/app.js
import React from 'react'
import ReactDOM from "react-dom";
import { Router, Route, Link, hashHistory } from 'react-router'

const users = [
  {name: "alice"}, {name: "bob"}, {name: "charlie"}
];

const App = React.createClass({
  render() {
    return (
      <div>
        <ul>
          <li><Link to="/">Home</Link></li>
          <li><Link to="/user">Users</Link></li>
        </ul>
        <h1>Hello, React!</h1>
        <p>This is an example site using <em>react-router</em>.</p>
        {this.props.children}
      </div>
    )
  }
})

const Users = React.createClass({
  render() {
    return (
      <div>
        <h2>Users</h2>
        <ul>
          {users.map(user => (
            <li key={user.name}><Link to={`/user/${user.name}`}>{user.name}</Link></li>
          ))}
        </ul>
      </div>
    )
  }
})

const User = React.createClass({
  render() {
    return (
      <div>
        <h2>User: {this.props.params.userName}</h2>
        <p>Welcome to {this.props.params.userName}&apos;s page!</p>
      </div>
    )
  }
})

const NoMatch = React.createClass({
  render() {
    return (
      <div>
        <h2>Oops... something is wrong</h2>
      </div>
    )
  }
})

ReactDOM.render(
  <Router history={hashHistory}>
    <Route path="/" component={App}>
      <Route path="/user" component={Users}/>
      <Route path="/user/:userName" component={User}/>
      <Route path="*" component={NoMatch}/>
    </Route>
  </Router>,
  document.getElementById("content")
);

上のコードは、/userにアクセスしたときハードコードされた各ユーザのページへのリンクを含むページ、/user/:userNameにアクセスしたとき各ユーザのページを表示する。 ここでは、簡略化のためハードコードされていないユーザ名についてもページ内容が表示されるようになっている。 また、<Link to="foo"></Link>におけるfooは、<Route path="foo"/>fooと対応している。

コンパイルして表示してみる

npm経由でWebpackを実行し、上のスクリプトJavaScriptコードにコンパイルしてみる。

$ npm run build

> helloreact2@1.0.0 build /home/user/tmp/helloreact2
> rm -rf scripts/*.js && webpack

Hash: e10002df15950c272793
Version: webpack 1.12.15
Time: 5099ms
    Asset    Size  Chunks             Chunk Names
bundle.js  851 kB       0  [emitted]  main
    + 223 hidden modules

Python等を用いてWebサーバを起動した後、ブラウザでindex.htmlにアクセスした際のスクリーンショットを示すと次のようになる。

$ python -m SimpleHTTPServer
Serving HTTP on 0.0.0.0 port 8000 ...

f:id:inaz2:20160415175159p:plain

適当にリンクをクリックすると、JavaScriptによるページの書き換えが行われ、対応するページ内容が表示されることが確認できる。 また、履歴を戻ると前のページ内容に更新されることが確認できる。 なお、URLに?_k=44vmmnのようなパラメータがついているが、これはPOSTされたデータなどを保存するために使われている(参考)。

関連リンク