これまでルーティング関連の記事では以下のような内容を取り上げてきました。
今までは以下のように直接
を書いてルーティングの定義を行ってきましたが、今回はこれらの設定をJSONなどで定義し、いつでも外部のファイルに出せるようにします。
<!-- befor --> <Switch> <Route exact path="/" component={Home} /> <Route exact path="/auth" component={Auth} /> <Route path="/area/:cd" component={Meisan} /> </Switch>
このままでも動くんですけどね。ロジックと設定は分離して置いた方がメンテしやすくなりますからね。
名産品図鑑アプリを作る その4
実行結果
挙動はこれまでと同じです。 s3-us-west-2.amazonaws.com
ソースコード
App.js
import React, { Component } from 'react'; import { BrowserRouter as Router, Route, Link, Switch, Redirect } from "react-router-dom"; import './App.css'; class App extends Component { constructor(props){ super(props); // ここにルーティングの設定をまとめる this.routes = [ {path:"/", component:Home, type:"public", exact:true}, {path:"/index.html", component:Home, type:"public", exact:true}, {path:"/auth", component:Auth, type:"public"}, {path:"/private", component:PrivatePage, type:"private", auth:"/auth"}, {path:"/area/:cd", component:Meisan, type:"public"}, {component:NoMatch, type:"public"} ]; } render() { return ( <Router> <div className={"container"}> {/* ナビゲーション */} <ul className={"gnavi"}> <li><Link to="/">名産品図鑑</Link></li> <li>┣ <Link to="/area/1">埼玉県</Link></li> <li>┣ <Link to="/area/2">島根県</Link></li> <li>┣ <Link to="/404">404</Link></li> <li>┗ <Link to="/private">?????</Link></li> <li> </li> <li><Link to="/auth">認証</Link></li> </ul> {/* ここから下が実際のコンテンツに置き換わる */} <Switch> {this.routes.map((route, i) => { let type = route.type; delete route.type; switch( type ){ case "public": return( <Route key={i} {...route} /> ); case "private": return( <PrivateRoute key={i} {...route} /> ); } })} </Switch> </div> </Router> ); } } /*====================================================*/ /* ここから下は前回と同じです */ /*====================================================*/ /** * トップページ */ function Home(){ return ( <div className={"item"}> <h2>名産品図鑑</h2> <ul> <li><Link to="/area/1">埼玉県<br /><img src="/image/saitama.png" alt="埼玉県" /></Link></li> <li><Link to="/area/2">島根県<br /><img src="/image/shimane.png" alt="島根県" /></Link></li> </ul> </div> ); } /** * 認証状態を保持する変数 */ var UserStatus = { auth: false //true:ログイン, false:未ログイン }; /** * 簡易的な認証ページ */ class Auth extends Component{ constructor(props){ super(props); this.state = { auth: UserStatus.auth } this.onLogin = this.onLogin.bind(this); this.onLogout = this.onLogout.bind(this); } onLogin(e){ e.preventDefault(); this.setState({auth:true}); UserStatus.auth = true; } onLogout(e){ e.preventDefault(); this.setState({auth:false}); UserStatus.auth = false; } render(){ let desc = UserStatus.auth? "現在ログイン中です":"ログインしていません"; let button = UserStatus.auth? <button onClick={this.onLogout}>ログアウト</button>:<button onClick={this.onLogin}>ログイン</button>; return ( <div className={"item"}> <h2>認証ページ</h2> <p>{desc}</p> <form> {button} </form> </div> ); } } /** * ログインチェック付きRouter */ class PrivateRoute extends Component{ constructor(props){ super(props); } render(){ if( UserStatus.auth ){ return( <Route path={this.props.path} component={this.props.component} /> ); } else{ return( <Redirect to={this.props.auth} /> ); } } } /** * 秘密のページ */ function PrivatePage(){ return( <div className={"item"}> <h2>南極</h2> <img src="/image/nankyoku.png" alt="北極" /> <ul> <li>ぺんぎん</li> <li>アザラシ</li> <li>オーロラ</li> </ul> </div> ); } /** * 404 */ function NoMatch(){ return ( <div className={"item"}> <h2>URLが存在しません</h2> </div> ); } /** * 名産品 Component */ class Meisan extends Component{ constructor(props){ super(props); this.area =[ {cd:1, name:"埼玉県", img:"/image/saitama.png", meisan:["十万石まんじゅう", "深谷ねぎ", "草加せんべい", "いも"]}, {cd:2, name:"島根県", img:"/image/shimane.png", meisan:["しじみ", "あご野焼", "出雲そば", "ぶどう"]} ]; } render(){ let cd = this.props.match.params.cd; if( (0 < cd) && (cd <= this.area.length) ){ let area = this.area[cd - 1]; let key = 1; let li = area.meisan.map( val => <li key={key++}>{val}</li> ); return ( <div className={"item"}> <h2>{area.name}</h2> <img src={area.img} alt="area.name" /> <ul> {li} </ul> </div> ); } else{ return ( <div className={"item"}> <h2>Not Found</h2> </div> ); } } } export default App;
App.css
CSSは前回から変更はありません。
ul{ list-style: none; } a:hover{ color: red; } .container{ display: inline-flex; /* 横に並べる */ } .gnavi{ order: 1; width: 80px; height: 500px; padding: 10px; margin-right: 10px; background-color: skyblue; } .item{ order: 2; }
解説
設定をJSON化
今回はAppクラス内で定義していますが、実際には外部のファイルに出しimport
してあげるのが良いでしょうか。
constructor(props){ super(props); // ここにルーティングの設定をまとめる this.routes = [ {path:"/", component:Home, type:"public", exact:true}, {path:"/index.html", component:Home, type:"public", exact:true}, {path:"/auth", component:Auth, type:"public"}, {path:"/private", component:PrivatePage, type:"private", auth:"/auth"}, {path:"/area/:cd", component:Meisan, type:"public"}, {component:NoMatch, type:"public"} ]; }
- 各項目は
に渡す値をそのまま書いてあげます。 type
属性はComponentに切り替えに利用する物です。最終的に削除します。exact
属性のように属性名だけ渡す項目は、true
を値として指定しておきます。
JSONをに変換
this.routes
配列をぐるぐる回しながら、type毎にComponentを出力しています。
<Switch> {this.routes.map((route, i) => { let type = route.type; delete route.type; switch( type ){ case "public": return( <Route key={i} {...route} /> ); case "private": return( <PrivateRoute key={i} {...route} /> ); } })} </Switch>
ドットが連続しているコイツ→...
ですが、これはスプレッド演算子と呼ばれている、ES2015から追加された新機能です。何か省略しているわけではありませんw
例えば以下のような場合、funcには配列そのものが渡されますが、
let foo = [1, 2, 3]; func1(foo); function func1(a){ console.log(a); }
スプレッド演算子を利用した場合は、配列が展開された状態で渡されます。
let foo = [1, 2, 3]; func2(...foo); function func2(a, b, c){ console.log(a, b, c); }
まぁこれだけだとピンと来ないと思いますので、上記のコードをブラウザのConsoleなどで実際に実行してみてください。