ハンズオンのベースになるリポジトリです。このコードを修正していきます。
- React.js
- Material UI
- React Router(今回学ぶこと)
- Webpack
- Style Loader
$ git clone https://github.com/mokumoku-temple/react-handson_vol03
$ cd ./react-handson_vol03
$ npm install
$ npm start
localhost:1029
にアクセスすると、下記のページが表示されます。ハンバーガーボタンを押すとメニューが表示されます。
このサンプルを、react-router
を使いながらSPA(Single Page Application)化していきます。
今回は、主にscripts
ディレクトリ以下のファイルを編集していきます。
Home
とList
という2つのページをもつアプリケーションを作成します。
まずは、それぞれのページ用のjsファイルを作成します。
containers/home/index.js
containers/list/index.js
ファイル名には、一般的にindex.js
という名前がよく使われます。これは、importしたときに自動的にそのディレクトリ以下のindex.js
を読み込んでくれるためです。
なので、たとえばcontainers/home/index.js
をimportするときは、
import Home from './containers/home';
となります。
home/index.js
とlist/index.js
は、次のようにします。JSXの部分は、ページの違いがわかれば何でも良いです。
import React, { Component } from 'react';
export default class HomePage extends Component {
render() {
return (
<div>
<h2>Home</h2>
</div>
);
}
}
import React, { Component } from 'react';
export default class ListPage extends Component {
render() {
return (
<div>
<h2>List</h2>
</div>
);
}
}
ヘッダ部分など、どのページでも共通で使うコンポーネントをapp/index.js
に書きます。
scripts/containers/app/index.js
を新規作成して、そこにscripts/index.js
の内容をコピペします。
コピペした後に、scripts/containers/app/index.js
に次の変更を加えます。
MuiThemeProvider
のタグを削除(閉じタグも忘れずに)injectTapEventPlugin()
を削除- 最後の閉じdivタグの前に、
{this.props.children}
を追加
最終的には、次のようなコードになります。(よくわからなくなったらコピペしましょう)
import React, { Component } from 'react';
import AppBar from 'material-ui/AppBar';
import Drawer from 'material-ui/Drawer';
import MenuItem from 'material-ui/MenuItem';
export default class App extends Component {
constructor(props) {
super(props);
this.state = {
showMenu: false,
};
}
onMenuStateChange(showMenu) {
this.setState({ showMenu });
}
render() {
return (
<div>
<AppBar
title="React HandsOn vol.03"
onLeftIconButtonTouchTap={() => this.onMenuStateChange(true)}
/>
<Drawer
docked={false}
open={this.state.showMenu}
width={200}
onRequestChange={show => this.onMenuStateChange(show)}
>
<MenuItem>Home</MenuItem>
<MenuItem>List</MenuItem>
</Drawer>
{/* ここまでヘッダ部分 */}
{/* ここにページの要素が入る */}
{this.props.children}
</div>
);
}
}
ルーティングに必要なモジュールと、先ほど作成したコンポーネントを読み込みます。
import { Router, Route, IndexRoute, hashHistory } from 'react-router';
import App from './containers/app';
import Home from './containers/home';
import List from './containers/list';
ルートコンポーネントを次のように書き換えます。
class Root extends Component {
render() {
return (
<MuiThemeProvider>
<Router history={hashHistory}>
<Route path="/" component={App}>
<IndexRoute component={Home} />
<Route path="/list" component={List} />
</Route>
</Router>
</MuiThemeProvider>
);
}
}
react-router
で使うモジュールの概要。
名前 | 概要 |
---|---|
Router | このエレメントに含まれる部分が、ルーティングの対象になる |
hashHistory | ブラウザの履歴管理をするモジュール |
Route | 「このパスのときはこのコンポーネントを表示する」という定義 |
IndexRoute | インデックスパス(/ )へのアクセスのときに表示されるコンポーネントを指定 |
ここまでで、ルーティングの設定は完了です。次のURLにアクセスしてみましょう。
ページ遷移用のリンク(HTMLで言うところのhref
)を作成します。
ヘッダのメニューにあるリストを、リンクに書き換えます。
import
を追加。
import { Link } from 'react-router';
メニューを<Link>
で囲む。
<Link to="/"><MenuItem>Home</MenuItem></Link>
<Link to="/list"><MenuItem>List</MenuItem></Link>
これで、メニューをクリックするとページ遷移するはずです。
href
を使って、自分でページ遷移を書くこともできなくはないですが、react-router
を使っているうちは基本的に<Link>
を使いましょう。
説明し辛いのでとりあえず書きましょう。
作るのはこんな感じのList - Detail
構成のページです。
本当はajaxとかでデータを取ってきたら良いのですが、難しそうなのでやめておきました。 Listのデータを別ファイルで定義して、読み込みます。コピペで良いです。
export default [
{
id: 1,
title: 'hoge',
contents: `
hogehoge
ほげほげ
ホゲホゲ
`,
},
{
id: 2,
title: 'huga',
contents: `
hugahuga
ふがふが
フガフガ
`,
},
];
次にリストページに、上で定義したアイテムを表示します。
importを追加。
import { Link } from 'react-router';
import { List, ListItem } from 'material-ui/List';
import Items from './items';
JSX部分は以下のように修正します。長い所は、リストの内容をすべて表示する呪文です。
export default class ListPage extends Component {
render() {
return (
<div className="list">
<List>
{Items.map(item => <Link key={item.id} to={`/list/${item.id}`}><ListItem primaryText={item.title} /></Link>)}
</List>
{/* ここにdetailが表示される */}
{this.props.children}
</div>
);
}
}
ここまでで、リストは表示されるようになりましたが、まだdetailを表示するルーティングを書いていないので、クリックしてもエラーが出ます。
詳細を表示するdetails
コンポーネントを作成します。
import React, { Component } from 'react';
import Items from './items';
export default class Detail extends Component {
render() {
// 何も選択されていないときの表示
if (!this.props.params.id) {
return (
<div>リストから選択してください</div>
);
}
// 表示するべきitemを探す(呪文)
const item = Items.filter(i => i.id === +this.props.params.id)[0];
return (
<div>
<h2>{item.title}</h2>
<p>{item.contents}</p>
</div>
);
}
}
次に、detailsを表示するためのルーティングを追記します。
index.js
のListページのルーティングを次のように書き換えます。
pathで指定している/:id
は、「任意の文字列を許容し、その文字列をid
という名前のパラメータとしてコンポーネントが受け取る」という意味です。
先ほどのdetail
では、this.props.params.id
という形で、渡されたidを受け取っています。
import Detail from './containers/list/detail';
<Route path="/list" component={List}>
<IndexRoute component={Detail} />
<Route path=":id" component={Detail} />
</Route>
リストの要素をクリックすると、詳細情報が表示されます。
- 自分でコンポーネント、ルートを追加してみる
- cssファイルを追加してみる
- less/sass/stylusなどを組み込んでみる