はじめてのReact #24 「ルーティングに対応する」URLパラメーター編 react-router@v5

前回はReact Routerの基本的な機能を利用し簡単な図鑑アプリを作成しました。

今回はリクエストを受けたURLの一部をパラメーターとして受け取ってみます。 例えばブログやTwitterのようなアプリで特定のIDの記事を表示したい場合/posts/view/123といった形式のURLにアクセスするとします。ここで問題になるのは1000個の記事があった場合、1000個のを定義する必要が出てくるのかという点ですね。こういった場合に特定のパターンのURLは一部の文字列をパラメーターとして設定することができます。

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

前回と同様に都道府県別に名産品を表示する簡単なWebアプリです。 実行結果は同じですが、コードを大幅に書き換えています。

実行結果

s3-us-west-2.amazonaws.com

ソースコード

App.js

import React, { Component } from 'react';
import { BrowserRouter as Router, Route, Link, Switch } from "react-router-dom";
import './App.css';

class App extends Component {
  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>
          </ul>

          {/* ここから下が実際のコンテンツに置き換わる */}
          <Switch>
            <Route exact path="/" component={Home} />
            <Route exact path="/index.html" component={Home} />
            <Route path="/area/:cd" component={Meisan} />
            <Route component={NoMatch}/>
          </Switch>
        </div>
      </Router>
    );
  }
}

/**
 * トップページ
 */
function Home(){
  return (
    <div className={"item"}>
      <h2>名産品図鑑</h2>
      <ul>
        <li><Link to="/area/1">埼玉県<br /><img src="/image/saitama.png" /></Link></li>
        <li><Link to="/area/2">島根県<br /><img src="/image/shimane.png" /></Link></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} />
          <ul>
            {li}
          </ul>
        </div>
      );
    }
    else{
      return (
        <div className={"item"}>
          <h2>Not Found</h2>
        </div>
      );
    }
  }
}

export default App;

App.css

CSSは前回から変更はありません。

a:hover{
  color: red;
}
ul{
  list-style: none;
}

.container{
  display: inline-flex;  /* 横に並べる */
}
.gnavi{
  order: 1;
  width: 80px;
  height: 500px;
  padding: 10px;
  margin-right: 10px;
  background-color: skyblue;
}
.item{
  order: 2;
}

解説

URLの一部をパラーメーターとして受け取る

今回のポイントは以下ですね。

  <Route path="/area/:cd" component={Meisan} />

他のフレームワークなどでもよく見かける書式ですが、path属性を上記のように記述することで:cdの部分にどのような文字列が来てもこのルーティング設定が適用されます。またここで:cdにマッチした値はComponentにpropsとして渡されます。

例えば/area/123へアクセスがあった場合、Meisan Componentではprops.match.params.cdを参照することで123の値を取り出すことができます。

class Meisan extends Component{
  constructor(props){
    props.match.params.cd;
  }
}

複数同時にマッチするのを防ぐ

のpath属性の指定方法によっては、複数同時に実行されてしまう可能性があります。

  <Route path="/area/1" component={Home} />
  <Route path="/area/:cd" component={Meisan} />

これを防ぐためにはReactRouterのを利用します。冒頭のimportSwitchの呼び出しを忘れずに。

<Switch>
  <Route path="/area/1" component={Home} />
  <Route path="/area/:cd" component={Meisan} />
</Switch>

の中に列挙することで、どれか一つだけが実行されます。上から順番に比較され最初にマッチした物が実行されるようです。逆にグローバルナビなど同時に実行したい物である場合はの外に出しておく必要があります。

いずれにもマッチしない場合のルーティング

どのルーティングにもマッチしない場合はのpath属性を指定しないことで、いわゆる404 NotFound時の処理を定義することができます。

<Switch>
  <Route exact path="/" component={Home} />
  <Route path="/area/:cd" component={Meisan} />
  <Route component={NoMatch}/>
</Switch>

続き

blog.katsubemakito.net

参考ページ