はじめてのReact #26 「ルーティングに対応する」設定をJSONにまとめる編 react-router@v5

  • このエントリーをはてなブックマークに追加
  • LINEで送る

これまでルーティング関連の記事では以下のような内容を取り上げてきました。

  • 第23回 基本的なReactRouterの使い方
  • 第24回 URLの一部をパラメーターとして受け取る
  • 第25回 認証とリダイレクト

今までは以下のように直接<Route>を書いてルーティングの定義を行ってきましたが、今回はこれらの設定をJSONなどで定義し、いつでも外部のファイルに出せるようにします。

<!-- befor -->
<Switch>
  <Route exact path="/" component={Home} />
  <Route exact path="/auth" component={Auth} />
  <Route path="/area/:cd" component={Meisan} />
</Switch>

このままでも動くんですけどね。ロジックと設定は分離して置いた方がメンテしやすくなりますからね。

- Sponsored Link -

名産品図鑑アプリを作る その4

実行結果

挙動はこれまでと同じです。

ソースコード

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"}
    ];
  }
  • 各項目は<Router>に渡す値をそのまま書いてあげます。
  • type属性はComponentに切り替えに利用する物です。最終的に削除します。
  • exact属性のように属性名だけ渡す項目は、trueを値として指定しておきます。

JSONを<Route>に変換

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などで実際に実行してみてください。

参考ページ

- Sponsored Link -

同じカテゴリの記事

Donate

投げ銭お待ちしております!

BTC3A9nH1j7qQdKrSTrmnEdweo6zPqpHBmkxC
ETH0x1aE0541198D1F9f2908a25C35032A473e74D3731
XPXaQ9zv65F9ovfoMBrFGiPRG47aSHFhy8SX
MONAMTKgzSiS5BDueZkRCHySih24TGFwHThaDQ (MonaCoin)
ZNYZhnpf4RFYVQTAQiyoJg9dGoeC4bgT3BoSy (BitZeny)

ご質問やリクエストなどお気軽に。メールアドレスの入力は任意です。書き込みが反映されるまで時間がかかります。

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください