React-Routerを試してみた


実際に使ったことがなかったのでお試し程度に触ってみました。

## 事前準備

webpackとreactをインストール。

> この記事ではReact Router v3の内容です。
> v4になって変更点が多々ありますのでそのままでは動作しません。
> (2018/3/1)ようやくv4の記事も書きました。[Webpack4を試すついでReact-Router4](https://qiita.com/yaaah93/items/579d04fbd925ac2fbc2a)

```bash
$ npm install --save react react-dom react-router
$ npm install --save-dev webpack babel-core babel-loader babel-preset-es2015 babel-preset-react
```

Webpackのconfigは以下の通り

```js:webpack.config.js
var path = require('path');
var webpack = require('webpack');

module.exports = [
    {
        context: path.join(__dirname, 'src/js'),
        entry: {
            app: './app.js'
        },
        output: {
            path: path.join(__dirname, 'public/javascripts'),
            filename: '[name].js'
        },
        module: {
            loaders: [{
                test: /\.js$/,
                exclude: /node_modules/,
                loader: 'babel',
                query: {
                    presets: ['react', 'es2015']
                }
            }]
        }
    }
]
```

## 画面表示

まずは画面表示から。
ベースとなるコンポーネントを作成します。

```js:app.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

class App extends Component {
	render() {
		return (
			<h1>Hello, World</h1>
		);
	}
}

render((
	<Router history={browserHistory}>
		<Route path="/" component={App} />
	</Router>
), document.querySelector('#app'));
```

まずはRouter、Route、browserHistoryを読み込ます。
Routerコンポーネントの中にRouteコンポーネントを読み込ませるようにします。
browserHistoryはURL履歴の設定で他にもhashHistoryやcreateMemoryHistoryがありますが今回はbrowserHistoryを使ってみました。
RouteのpathはURLのパターンとcomponentはそのままですがコンポーネントを指定します。

あとはこのjsをwebpackを使ってビルドしてhtmlで読み込むようにします。


```html:index.html
<!DOCTYPE html>
<html>
<head>
	<title>Sample</title>
	<link rel='stylesheet' href='/stylesheets/style.css' />
</head>
<body>
	<div id="app"></div>
	<script src="/javascripts/app.js"></script>
</body>
</html>
```

とりあえずはこれでHello,Worldが表示されるようになります。

## 画面遷移

次に画面遷移を行います。
リンクなどで画面遷移する場合はLinkコンポーネントを読み込みます。

```js:app.js
import { Router, Route, browserHistory, Link } from 'react-router';
```

Linkコンポーネントにはリンク先URLを設定します。

```js:app.js
<Link to="/a">Page A</Link>
```

そのほかの考え方は画面表示と同じなので表示するコンポーネントとURLに合わせてRouteコンポーネントを追加します。

```js:app.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory, Link } from 'react-router';

class App extends Component {
	render() {
		return (
			<div className="wrapper">
				<header>
					<h1>App</h1>
				</header>
				<nav>
					<ul>
						<li><Link to="/">HOME</Link></li>
						<li><Link to="/a">Page A</Link></li>
						<li><Link to="/b">Page B</Link></li>
					</ul>
				</nav>
			</div>
		);
	}
}

class PageA extends Component {
	render() {
		return (
			<div className="wrapper">
				<header>
					<h1>PageA</h1>
				</header>
				<nav>
					<ul>
						<li><Link to="/">HOME</Link></li>
						<li><Link to="/a">Page A</Link></li>
						<li><Link to="/b">Page B</Link></li>
					</ul>
				</nav>
			</div>
		);
	}
}

class PageB extends Component {
	render() {
		return (
			<div className="wrapper">
				<header>
					<h1>PageB</h1>
				</header>
				<nav>
					<ul>
						<li><Link to="/">HOME</Link></li>
						<li><Link to="/a">Page A</Link></li>
						<li><Link to="/b">Page B</Link></li>
					</ul>
				</nav>
			</div>
		);
	}
}

render((
	<Router history={browserHistory}>
		<Route path="/" component={App} />
		<Route path="/a" component={PageA} />
		<Route path="/b" component={PageB} />
	</Router>
), document.querySelector('#app'));
```

リンク部分を共通化せずに書くとコーディング量が増えるのでHeaderコンポーネントを作って...という手順になりますがReact-RouterではRouteコンポーネントを親子関係にして、かつIndexRouteコンポーネントを使うことでぐっとコーディング量を減らすことができます。

```js:app.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory, Link, IndexRoute } from 'react-router';

class Wrapper extends Component {
	render() {
		let { children } = this.props;
		return (
			<div className="wrapper">
				<header>
					{children}
				</header>
				<nav>
					<ul>
						<li><Link to="/">HOME</Link></li>
						<li><Link to="/a">Page A</Link></li>
						<li><Link to="/b">Page B</Link></li>
					</ul>
				</nav>
			</div>
		);
	}
}

class App extends Component {
	render() {
		return (
			<h1>App</h1>
		);
	}
}

class PageA extends Component {
	render() {
		return (
			<h1>PageA</h1>
		);
	}
}

class PageB extends Component {
	render() {
		return (
			<h1>PageB</h1>
		);
	}
}

render((
	<Router history={browserHistory}>
		<Route path="/" component={Wrapper}>
			<IndexRoute component={App} />
			<Route path="/a" component={PageA} />
			<Route path="/b" component={PageB} />
		</Route>
	</Router>
), document.querySelector('#app'));
```

少しページ構造的にヘッダーが上にきておかしなことになってますがサンプルということで...。
ここではWrapperコンポーネントというを作っていますがこれがレイアウトのような役割をしてくれます。

## パラメータの受け渡し

編集画面に遷移する場面ではパラメータを渡して/users/1のようなURL設計をするかと思いますがこれも簡単に設定できます。
Routeコンポーネントのpathに:idと設定するとパラメータを受け取ることができます。

```js:app.js
<Route path="/b/:id" component={PageB} />
```

受け取る際はpropsのparamsで受け取ることができます。

```js:app.js
class PageB extends Component {
	render() {
		let {id} = this.props.params;
		return (
			<h1>PageB - {id}</h1>
		);
	}
}
```

## まとめ

長くなりそうなので...。
クエリパラメータの設定やPOST送信などまだまだ試せていないことはありますが単純な画面遷移をするようなアプリであれば十分に使えそうなかんじはします。

今回確認用に作ったjsを載せておきます。試してみたい方はどうぞ。
（適当に作ったのであまり良い例ではないと思いますが...）

```js:app.js
import React, { Component } from 'react';
import { render } from 'react-dom';
import { Router, IndexRoute, Route, browserHistory, Link } from 'react-router';

let users = [
	{ id: 1, name: 'Suzuki Ichiro' },
	{ id: 2, name: 'Tanak Jiro' },
	{ id: 3, name: 'Sato Hanako' }
];

class Wrapper extends Component {
	render() {
		let { children } = this.props;
		return (
			<div className="wrapper">
				<header>
					<h1>React Router</h1>
				</header>
				<main>
					{children}
				</main>
			</div>
		);
	}
}

class Index extends Component {
	render() {
		return (
			<div id="contents">
				<h2>Index</h2>
				<div className="action">
					<Link to="/users/create">Create</Link>
				</div>
				<ul>
				{users.map(user =>
					<li key={user.id}>
						<Link to={`/users/${user.id}`}>{user.name}</Link>
					</li>
				)}
				</ul>
			</div>
		);
	}
}

class Create extends Component {
	render() {
		return (
			<div id="contents">
				<h2>Create</h2>
				<Form />
			</div>
		);
	}
}

class Edit extends Component {
	render() {
		let { params } = this.props;
		let user = users.filter(user => user.id == params.id);
		return (
			<div id="contents">
				<h2>Edit</h2>
				<Form user={user[0]}/>
			</div>
		);
	}
}

class Form extends Component {
	constructor(props) {
		super(props);
		let { user } = this.props;
		this.state = { name: user ? user.name : '' }
	}
	handleKeyDown(e) {
		this.setState({ name: e.target.value });
	}
	handleSubmit(e) {
		e.preventDefault();
		let { user } = this.props;
		let { name } = this.state;
		if (!user) {
			let id = 0;
			users.forEach(user => {
				if (user.id > id) id = user.id;
			});
			users.push({ id: id + 1, name: name });
		} else {
			users = users.map(data => {
				if (data.id == user.id) {
					return { id: user.id, name: name };
				}
				return data;
			});
		}
		browserHistory.push('/');
	}
	render() {
		let { name } = this.state;
		return (
			<form onSubmit={this.handleSubmit.bind(this)}>
				<input type="text" name="name" value={name} onChange={this.handleKeyDown.bind(this)}/>
			</form>
		);
	}
}

render((
	<Router history={browserHistory}>
		<Route path="/" component={Wrapper}>
			<IndexRoute component={Index} />
			<Route path="/users/create" component={Create} />
			<Route path="/users/:id" component={Edit} />
		</Route>
	</Router>
), document.querySelector('#app'));
```