Lesson 3

Reactの基礎文法

Lesson 3 Chapter 1
JSX記法

Reactは、WebアプリケーションのUIを構築するためにMeta(旧Facebook)社によって開発されたJavaScriptライブラリです。Reactでは、UIをコンポーネントと呼ばれる小さな部品単位で構築し、それらの組み合わせにより複雑なUIを構築することができます。 UIをより簡単に構築できるように、ReactはJSXという独自の記法を採用しています。

JSX記法とは

Reactには、JSX(JavaScript XML)と呼ばれる記法があります。JSXはJavaScriptの機能を拡張し、HTMLに非常に似た構文でReactで作成したコンポーネントを表現できるようにした記法です。JSXはReactのコンポーネントを直感的に実装できるようにするために開発されました。JSXはReact以外でも、PreactInfernoStencilなどのライブラリでの採用例があります。JSXによる記述例を以下に示します。コードのroot.render( . . . )の" . . . "の部分がJSX記法になります。

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
  <div>
    <h1>First <span style={{color:"red"}}>React!!</span></h1>
  </div>
);

上記のコードをReact環境で実行すると、ブラウザで以下の表示を確認できます。

2023-03-09-15-15-57.png 黒字で"First"、赤字で"React!!"が表示されています。

JSX以外の記述方法としては、React.createElement()メソッドを使用する方法があります。React.createElement()メソッドを使用して同じ表示を得るコードは以下になります。

const c = React.createElement("span", { style : {color : "red"} }, "React!!");
const b = React.createElement("h1", null, "First ", c);
const a = React.createElement("div",null, b);
                      
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(a);

いかがでしょう。最初に示したJSX記法の方がUIをHTML的な構文で記述できるので分かりやすいのではないかと思います。このように、可読性が低下する理由から、React.createElement()メソッドではなくJSXの使用が推奨されています。JSXではHTMLに存在するタグ以外にも、自分で定義した表示用の関数をHTMLのタグのように記述できます。まず、「こんにちは!」を表示する関数の作成を通して、JSX記法の特徴を理解していきましょう。

テスト用プロジェクトの作成

はじめに、Reactのプロジェクトを作成します。ここではプロジェクト名を"hello-in-japanese"としましたが、任意の名前で結構です。プロジェクトを作成すると、デフォルトで以下のようなフォルダ構成になると思います。以降、特に断りがなければ以下のフォルダ構成で話を進めていきます。

.
└── hello-in-japanese
    ├── public
    │   ├── favicon.ico
    │   ├── index.html
    │   ├── logo192.png
    │   ├── logo512.png
    │   ├── manifest.json
    │   └── robots.txt
    ├── src
    │   ├── App.css
    │   ├── index.css
    │   ├── App.js
    │   ├── App.test.js
    │   ├── index.js
    │   ├── reportWebVitals.js
    │   ├── setupTests.js
    │   └── logo.svg
    ├── package.json
    ├── package-lock.json
    └── README.md

プロジェクト作成後、srcフォルダの中身を確認するとindex.jsというファイルがあるはずです。そのファイルを開き、以下のように修正します。修正したら"index.jsx"というように拡張子を変更して保存し、元々の"index.js"は削除してしまいましょう。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
                      
root.render(
  <React.StrictMode>
                      
  </React.StrictMode>
);

拡張子を"js"から"jsx"に変更しましたが、どちらでも動作は可能です。ただ、"jsx"はReactのコンポーネントファイルに使用される拡張子なので、一目で中身がReactであると分かるというメリットがあります。最初の2行の"import . . ."でReactの使用に必要なライブラリをindex.jsxで利用可能な状態にしています。コード下部のroot.render関数の引数として記述されたタグがブラウザの表示内容となります。root.render関数の引数には既にReact.StrictModeのタグが存在しますが、これはコードを検査するためのもので、UIには何も表示しないコンポーネントです。

レンダリングの仕組み

Reactにおけるレンダリングは、仮想DOMを用いた差分更新によって行われます。DOM(Document Object Model)はブラウザがWebページを読み込んで、ページの要素を内部的にオブジェクトツリーとして扱うための仕組みです。DOMにより、JavaScriptを使ってWebページの要素にアクセスしたり、変更を加えたりすることができます。具体的にDOMがどのようなものかを簡単な例で示します。以下のようなHTMLがあったとします。

<!DOCTYPE html>
<html>
  <head>
    <title>テスト</title>
  </head>
  <body>
    <p>これはテストです</p>
  </body>
</html>

こちらの文書構造はDOMによって以下のように表現できます。

document 
  └── html
          ├── head
          │       └── title
          │               └── #text: テスト
          └── body
                  └─── p
                          └── #text: これはテストです

仮想DOMは、React内部で保持する仮想的なDOMです。Reactが新しいコンポーネントの描画を行うと、仮想DOMが更新されます。この際、変更された部分のみを実際のDOMに反映させます。このような仕組みにより、Reactでは高速なレンダリングを実現しています。index.jsxのroot.render()メソッドは、Reactの仮想DOMを変数rootで指定した実際のDOMに挿入するために使用されます。具体的に、root.render()の挿入される場所を確認してみましょう。publicフォルダにあるindex.htmlを開くと以下が表示されます。

index.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8" />
    <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <meta name="theme-color" content="#000000" />
    <meta
      name="description"
      content="Web site created using create-react-app"
    />
    <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
    <link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
    <title>React App</title>
  </head>
  <body>
    <noscript>You need to enable JavaScript to run this app.</noscript>
    <div id="root"></div>
  </body>
</html>

内容を確認するのが目的なので、元々記述されていたコメントは省略しております。index.htmlのbodyタグで囲まれた要素がブラウザに表示されます。JavaScriptが使用できる環境ではnoscriptタグの内容は無視されますので表示されません。index.jsxの変数rootではidが"root"の要素を指定しているので、root.render()メソッドの引数の内容が挿入される場所は"<div id="root"></div>"の部分になります。例えば、このレッスンの最初に示した内容がindex.jsxに記述されていると、bodyタグの内容が以下になったことと同様になります。

<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root">
  <div>
    <h1>First <span style={{color:"red"}}>React!!</span></h1>
  </div>
</div>

関数を定義して文字列をレンダリングする

JSX記法によるレンダリングの練習として、文字列を表示する関数を作成してみましょう。index.jsxファイル内に「こんにちは!」を表示する関数を定義します。index.jsxにHelloInJapanese関数を以下に習って記述してみましょう。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
                      
const HelloInJapanese = () => {
                      
  return (
    <p>こんにちは!</p>
  );
                      
}
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
                      
root.render(
  <React.StrictMode>
                      
  </React.StrictMode>
);

続いて以下のコードをご覧ください。この関数の出力をレンダリングするために<React.StrictMode>と</React.StrictMode>の間に定義した関数を<HelloInJapanese/>という形で追記します。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
                      
const HelloInJapanese = () => {
                      
  return (
    <p>こんにちは!</p>
  );
                      
}
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
                      
root.render(
  <React.StrictMode>
    <HelloInJapanese />                     
  </React.StrictMode>
);

<HelloInJapanese/>のように定義した関数の戻り値がJSX記法である場合、関数名をタグの名前として記述することが出来ます。ここで注意していただきたいことがあります。それはユーザーが定義した関数をJSX記法で使用するためには関数名を大文字で始めなければならないという規則があるということです。このような理由から、この後JSXで使用する関数名は全て大文字で始まるようになってます。作業が終わったらファイルを上書き保存しましょう。保存できたら、Reactを立ち上げて出力をブラウザで確認してみましょう。コードにミスがなければ以下の写真のように「こんにちは!」が表示されます。

2023-02-18-10-55-21_.png 左上の領域に「こんにちは!」が表示されています。

複数の要素を返す関数の場合

JSX記法のルールとして、return文の内容が一つのタグで囲まれていなければならないということがあります。したがって、以下のコードはエラーとなってしまいます。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
                      
const HelloInJapanese = () => {
                      
  return (
    <p>こんにちは!</p>
    <p>いいお天気ですね!</p>
  );
                      
}
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
                      
root.render(
  <React.StrictMode>
    <HelloInJapanese />                     
  </React.StrictMode>
);

2つ以上のタグを出力する場合、FragmentというReactに用意されているタグあるいは空のタグで囲ってあげることでエラーを回避することができます。先ほどのコードにFragmentを適用したのが以下のコードです。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
                      
const HelloInJapanese = () => {
                      
  return (
    <Fragment>  {/*ここがFragmentの開始タグ*/}
      <p>こんにちは!</p>
      <p>いいお天気ですね!</p>
    </Fragment>  {/*ここがFragmentの終了タグ*/}
  );
                      
}
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
                      
root.render(
  <React.StrictMode>
    <HelloInJapanese />                     
  </React.StrictMode>
);

Fragmentの使用箇所には右側にコメントをしております。Fragmantはreactのライブラリからimportして使用します。Fragmentの代わりに空タグで囲う場合、以下のコードになります。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
                      
const HelloInJapanese = () => {
                      
  return (
    <>  {/*ここが空のタグの開始*/}
      <p>こんにちは!</p>
      <p>いいお天気ですね!</p>
    </>   {/*ここが空のタグの終了*/}
  );
                      
}
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
                      
root.render(
  <React.StrictMode>
    <HelloInJapanese />                     
  </React.StrictMode>
);

空のタグの使用箇所には右側にコメントをしております。いずれの方法でも、表示結果は以下の写真のようになります。

2023-02-18-10-53-12_.png 先ほどの「こんにちは!」に加えて「いいお天気ですね!」が表示されています。

複数の要素を返す方法として、Fragmantと空のタグの2つのタグを使用する方法を紹介しました。Fragmantはreactのライブラリからimportして使用しなければならず使いにくそうに感じますが、空のタグにない利点として、後ほど紹介するmap関数内でkeyをPropsとして使うことができるというメリットがあります。

Lesson 3 Chapter 2
コンポーネントの呼び出し方

コンポーネントで分割する利点

ReactではUIを部品単位で分割したものをコンポーネントと呼んでいます。コンポーネント毎にファイルを作成することにより得られるメリットは多くありますが、一番に挙げられることとして再利用性があります。コンポーネントは基本的に単一の機能を担当するため、1つのコンポーネントを別の場所で再利用することができます。例えば以下の図のように、テキスト入力という共通の機能が必要とされる場合に、コンポーネントに与えるProps(Propsは本レッスンの Chapter 6で説明します)を変更するだけで同じコンポーネントを使いまわすことが可能となります。

コンポーネント化のメリットの説明(再利用性).png コンポーネントには同一の機能であれば再利用できるというメリットがあります。

またメンテナンス性が向上するというメリットもあります。例えはあるアプリのヘッダの色を変更したいといった場合、通常ならHTMLを参照しながら、cssからヘッダのスタイルの定義部を探し出して書き換えるというように、2つのファイルを行き来する手間が生じます。一方、Reactのコンポーネント化を利用すると、下図のように同一ファイル内にスタイルを定義できるようになるので、変更が容易になるとともに管理がしやすくなります。

コンポーネント化のメリットの説明(メンテナンス性).png コンポーネント化によってメンテナンス性が向上するというメリットがあります。

以降では、App.jsxの作成を通してコンポーネントの基礎を学習します。

App.jsxの作成

App.jsxは、Reactのプロジェクトにおいてアプリケーション全体のルートコンポーネントとなるファイルで、アプリケーション全体を制御する役割を担っています。通常、このコンポーネントにおいてルーティング、グローバルStateの定義等のアプリケーション全体に関わる設定を行います。今回は前チャプターで表示した「こんにちは!」を表示する関数をApp.jsx内でコンポーネント化します。それではファイルを新規作成し、以下のコードに習ってApp関数を記述します(関数の中身はHelloInJapanese関数と同じです)。記述したら、srcフォルダにApp.jsxというファイル名で保存しましょう(srcフォルダにApp.jxファイルがある場合、削除してください)。

App.jsx
const App = () => {

  return (
                      
    <p>こんにちは!</p>
                      
  );
                      
}

App.jsxのexport

App.jsx内の関数を他のファイルでコンポーネントとして使用するためには、関数をexportしてあげる必要があります。以下のコードのように最下部に関数をexportする文を記述します。

App.jsx
const App = () => {

  return (
                      
    <p>こんにちは!</p>
                      
  );
                      
}

export {App};

exportに続いて中括弧を記述し、その中にexportしたい関数名を記述することで記述した関数を外部のファイルで使用できるようになります。一つのファイルに複数関数を定義しexportしたい場合、"export {funcA, funcB, . . . }"のように関数名をカンマで区切って記述します。

App.jsxのimport

App関数をindex.jsxにimportして表示させてみましょう。以下のコードのようにimport {App} from './App.jsx'を追加します。加えて、<React.StrictMode>の子要素として<App />を記述しましょう。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import {App} from './App.jsx'
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
                      
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);

関数をexportするファイルの"export { . . . }"の中括弧内に記述された関数の中から、importするファイルで必要な関数名を"import { . . . }"の中括弧内に記述してやることにより、記述した関数をimportすることができます。例えば関数をexportするファイルに"export {funcA, funcB, funcC}"と記述されており、そこからfuncAとfuncCのみimportしたいときは、"import {funcA, funcC} from ' . . . '"と記述します。上記の3行目では、"./App.js" からApp関数のみをimportするので、"import {App} from './App.js'"となります。ブラウザで表示を確認すると、以下のようにindex.jsxの中でHelloInJapanese関数を定義していた時と同様の出力が得られます。

2023-02-18-10-51-12_.png App.jsxのApp関数の出力として、「こんにちは!」が表示されています。

Lesson 3 Chapter 3
スタイルの適用

style属性の記述

ここまでは、Reactのコンポーネントの内容を装飾無しで表示してきました。ここからは少し装飾を加えていきましょう。先ほどの「こんにちは!」を表示したプログラムにcssを適用してみます。手始めに「こんにちは!」を赤色で表示してみましょう。App.jsxのpタグに以下のようなstyle属性を追加します。

App.jsx
const App = () => {

  return (
    <p  style={{ color:  "red" }}>こんにちは!</p>
  );
                      
}
                      
export {App};

return文において、タグの属性値あるいは子要素としてJavaScript式を記述するときは、式を中括弧で囲う必要があります(囲わないとエラーになります)。ここでは、style属性の値をJavaScript式で記述しているため"style={ . . . }"となっています。また、ReactでcssはJavaScriptのオブジェクトとして記述する必要があります。そのため、"{ color: "red" }"が記述されます。ブラウザで表示を確認すると、以下のように「こんにちは!」が赤色で表示されていることが確認できます。

2023-02-18-11-28-33_.png 「こんにちは!」が赤色で表示されています。

プロパティ名はキャメルケースで書くことに注意する

先ほどReactでcssはJavaScriptのオブジェクトとして記述すると述べました。ここで記述の仕方に注意が必要です。cssでは複数の単語から成るプロパティ名は単語の間を"‐"で繋ぎますが、JavaScriptではキャメルケースで記述します。キャメルケースは複数の単語から成る変数の2番目以降の単語の頭文字を大文字にするスタイルです。例えば、文字の大きさを指定するプロパティである"font-size"はJavaScriptでは"fontSize"とします。またプロパティの値はJavaScriptでは文字列として与えます。このような理由から、"{ color: "red" }"のように、redを""で囲っています。

キャメルケースの確認のために、先ほどの赤色の「こんにちは!」について文字サイズを変更してみましょう。以下のようにstyle属性値として定義したオブジェクトのプロパティに"fontSize: "50pt""と追記します。

App.jsx
const App = () => {

  return (
    <p  style={{ color:  "red", fontSize: "50pt"}}>こんにちは!</p>
  );
                      
}
                      
export {App};

ブラウザでの表示結果は以下のようになります。

2023-02-18-11-27-29_.png 「こんにちは!」が50ptで表示されています。

style属性値として直接JavaScriptオブジェクトを記述して指定する以外にも、事前に定義したオブジェクトを指定することも可能です。上記と同じスタイルをcontentStyleという変数名のオブジェクトとして事前に定義し、style属性に指定した書き方が以下になります。

App.jsx
const App = () => {

  const contentStyle = {
    color: "red",
    fontSize: "50pt"
  }
                    
  return (
    <p style={contentStyle}>こんにちは!</p>
  );
                    
}
                    
export {App};

Lesson 3 Chapter 4
イベント処理

イベント処理とは

ユーザーがウェブページ上で行った操作(クリック、マウスオーバー、キー入力等)に応じて、特定のプログラムを実行することをイベント処理といいます。ウェブページのボタンのクリックにより、対応する処理が実行されることが一般的な例です。イベント処理によって、ユーザーの操作に対して、リアルタイムに反応するウェブページやアプリケーションを実現することができます。イベント処理は、ウェブアプリケーションやモバイルアプリケーションなどの開発に欠かせない機能です。

Reactでのイベントの処理の書き方

ウェブページのユーザーのアクションを検出し、処理を実行するための関数をイベントハンドラといいます。イベントハンドラの中身はJavaScriptの関数であるため、イベントの種類や発生元の要素などに応じて異なる処理を設定できます。Reactでは、イベントハンドラをイベントを発火させる要素(ボタン、テキストボックス等)の属性として指定することでイベント処理を定義できます。Reactではイベントハンドラをキャメルケースで記述します。よく使用されるイベントハンドラには以下のものがあります。

イベントハンドラ名 発火するタイミング
onClick 要素をクリックした時
onChange フォーム要素の選択、入力内容が変更された時
onSubmit フォームを送信しようとした時
onKeyPress キーボードが押された時
onFocus 要素がフォーカスされた時
onBlur 要素がフォーカスを失った時
onMouseOver 要素にマウスが乗った時
onMouseOut 要素からマウスが離れた時
onScroll 要素がスクロールされた時

実際に、ボタンを押したときにアラートを表示する関数を実装を通してReactでのイベント処理を書き方を学習しましょう。

アラートを表示する関数の実装

以前作成した「こんにちは!」を表示するコンポーネントApp.jsxに以下のようにボタンを追加します。ここで、JSX記法ではreturnの中身が一つのタグで囲まれていないといけないので、p要素とbutton要素を空タグで囲みます。

App.jsx
const App = () => {

  return (
    <>
      <p>こんにちは!</p>
      <button>ボタン</button>
    </>
  ;

}
                    
export {App};

上記のコードにボタンを押したときにアラートを表示する関数を実装すると以下のようになります。

App.jsx
const App = () => {

  const onClickButton = () => {
    alert("アラート");
  }
                    
  return (
    <>
      <p>こんにちは!</p>
      <button onClick={onClickButton}>ボタン</button>
    </>
  );

}
                    
export {App};

button要素のイベントハンドラonClickにonClickButton関数を指定しています。onClickButton関数は"アラート"というメッセージのアラートを表示するように定義されています。

onClickイベントを発火させる

ブラウザでonClickイベントの発火を確認してみましょう。先ほどのプログラムを実行すると以下の画面が表示されます。

2023-02-18-16-05-43_.png 「ボタン」と書かれたボタンが追加されています。

「ボタン」と書かれたボタンをクリックすると、以下のように"アラート"というメッセージのアラートが表示されます。

2023-02-18-16-07-31_.png ボタンをクリックすると、「アラート」というメッセージのアラートが表示されます。

このように、イベントハンドラ名にイベントを定義した関数を指定することによりイベントを発火させることができます。

Lesson 3 Chapter 5
画像の表示

Reactにおける画像表示

画像の表示はウェブ開発に欠かせない技術です。Reactにおいても画像を表示させることが可能です。以降ではReactで画像を表示させるコンポーネントを作成していきます。

画像ファイルを保存する

Reactで画像を表示する準備として、srcフォルダの中に画像を保存するimagesというフォルダを作成します。imagesフォルダを作成したら表示したい画像をそこに保存します。画像はお手持ちのスマホの写真でも何でも構いません。この教材では著作権フリーのサイトからダウンロードした以下の写真を使用します。ファイル名は"test_image.jpg"として保存します。

2023-02-18-19-32-58.png テスト用の画像

画像ファイルのパスをインポートし、コンポーネントに表示させる

画像の準備が出来たので、Reactで表示させてみます。まず画像の表示用に以下に示す関数コンポーネントを作成します。作成出来たら、srcフォルダにcomponentsという名前でフォルダを作成し、その中に保存しましょう。ファイル名は”MyImage.jsx”とします。

MyImage.jsx
import test from '../images/test_image.jpg';

const MyImage = () => {
                      
  return (
    <img src={test} style={{width: "50%"}}/>        
  );
                      
}
                      
export {MyImage};

App.jsxの1行目にて先ほど作成したMyImageをインポートし、return文にて出力しています。実行すると、ブラウザ上で以下のように表示されます。

2023-02-18-20-13-35_.png テスト用の画像が表示されています。

Lesson 3 Chapter 6
親コンポーネントから子コンポーネントへ値を渡す

Propsとは

Reactでは、親コンポーネントが持つ変数や状態(State)に応じて、子コンポーネントの表示を変更することが良くあります。そのような時、親コンポーネントから子コンポーネントへ値を渡す必要があります。この親コンポーネントから渡す値のことをPropsといいます。

単方向データフローとは

以下に示す図のように、Reactではデータは親コンポーネントから子コンポーネントに流れます。このことを、単方向データフローいい、流れるデータがPropsとなります。逆に子コンポーネントから親コンポーネントにデータが流れることはありません。川を流れる水をデータとすると親コンポーネントが上流、子コンポーネントが下流にあるとイメージすると分かりやすいでしょう。

単方向データフローの説明.png Reactにおいて、データは親コンポーネントから子コンポーネントにのみ流れます。

Propsのルール

単方向データフロー以外にもPropsには大事なルールがあります。それは読み取り専用であるということです。よって、以下の2つの関数がPropsを受け取る子コンポーネントである時、上の例は問題ないですが、下の例はPropsのプロパティ値を書き換えしようとしているのでルール違反になります。ブラウザでは上の例のみ結果が表示されます。

const Add = (props) => {
  return <p>{props.a + props.b}</p>
}
const Add = (props) => {
  props.initVal += props.addVal;
  return <p>{props.initVal}</p>
}

以降から、コンポーネントの作成を通じてPropsの扱い方を学習していきます。

Propsの渡し方

Propsの説明のために、App.jsxのApp関数を親コンポーネント、MyMessage.jsxのMyMessage関数を子コンポーネントとし、ブラウザに表示するメッセージと文字のスタイルをPropsとしてApp関数からMyMessage関数が受け取るプログラムを作成します。まず、以下の内容のApp.jsxを作成します。

App.jsx
import {MyMessage} from './components/MyMessage'

const App = () => {
                      
  const message = "こんにちは";
  const messageStyle = {color:  "red", fontSize: "50pt"}
                      
  return <MyMessage message={message} style={messageStyle} />;
                      
}
                      
export {App};

ここで変数messageがブラウザに表示するメッセージ、変数messageStyleが文字のスタイルになります。これらはそれぞれMyMessageコンポーネントのmessageおよびstyle属性に渡されています。これらの属性がMyMessage関数のPropsとなります。以下の図はここまでの流れをイメージしたものです。

Propsを渡すイメージ1.png 変数messageとmessageStyleの受け渡しのイメージ

図のように上記のコードはMyMessage関数のオブジェクトPropsのmessageとstyleプロパティにApp関数の変数messageとmessageStyleをそれぞれ代入しています。messageは以下のようにMyMessageコンポーネントの子要素(開始タグと終了タグの間の内容)として渡すこともできます。

App.jsx
import {MyMessage} from './components/MyMessage'

const App = () => {
                      
  const message = "こんにちは";
  const messageStyle = {color:  "red", fontSize: "50pt"}
                      
  return <MyMessage style={messageStyle} >{message}</MyMessage>;
                      
}
                      
export {App};

messageについては、受け渡し方により受け取り側のMyMessage.jsxでの取り扱いが変わります。最初に示したApp.jsxをパターンA、後に示した方をパターンBとして受け取り方を見てみましょう。

Propsの受け取り方

MyMessage.jsxをcomponentsフォルダ内に作成します。最初にパターンAのPropsを受け取るMyMessage.jsxを記述します。パターンAの場合、以下のように記述します。

MyMessage.jsx
const MyMessage = (props) => {

  return <p style={props.style} >{props.message}</p>;
                      
}
                      
export {MyMessage};

MyMessage関数の引数としてpropsと記述しています(ここでは分かりやすくpropsとしていますが別の名前でも構いません)。先ほど図で示したようにApp関数のMyMessageコンポーネントに定義した属性名は、MyMessage関数の引数propsのプロパティとなります。よって、メッセージはprops.message、メッセージのスタイルはprops.styleとして取り出すことができます。続いてパターンBのPropsを受け取るMyMessage.jsxを記述します。パターンBの場合、以下のように記述します。

MyMessage.jsx
const MyMessage = (props) => {

  return <p style={props.style} >{props.children}</p>;
                      
}
                      
export {MyMessage};

パターンAとの違いはメッセージをprops.childrenとして取り出している点です。このように、親コンポーネントにおいて子コンポーネントの子要素として渡されたPropsはchildrenのプロパティ名で取り出すことができます。両パターンともブラウザでの表示に違いはありません。ちなみにブラウザでは以下のように表示されます。

2023-02-19-16-00-30_.png 赤色で「こんにちは」が表示されています。

Propsを分割代入してコードを簡潔にする

Propの取り出しに毎回"props."と記述していると、長いコードを記述する場合にコードが複雑になってきます。そのような時、オブジェクトの分割代入が有効になります。先ほどのバターンBのPropsを受け取るMyMessage関数にオブジェクトの分割代入を利用すると以下のようにしてPropsを取り出せます。

MyMessage.jsx
const MyMessage = (props) => {

  const {children, style} = props;
                      
  return <p style={style} >{children}</p>;
                      
}
                      
export {MyMessage};

"const {children, style} = props;"の部分が分割代入している部分です。propsのプロパティ名を"="の左側の中括弧内に記述することでプロパティを取り出すことができます。分割代入によって、コードに一行追加されますがそれ以降、"props."と記述する必要はなくなります。あるいはMyMessage関数の引数を指定する括弧内で分割代入する事も可能です。以下の例ではパターンBのPropsをMyMessage関数の引数を指定する括弧内で分割代入しています。

const MyMessage = ({children, style}) => {

  return <p style={style} >{children}</p>;
                      
}
                      
export {MyMessage};

このようにPropsを分割代入することによりコードを簡潔に記述することができます。

Lesson 3 Chapter 7
ルーティング

ルーティングとは

ユーザーがアクセスしたURLに対して、どのページを表示するかを決定することをルーティングといいます。Reactにおいては、URLに対応するコンポーネントを呼び出し、表示することでルーティングを実現します。ルーティングによって、複数のページを持つウェブアプリケーションを実現することができます。さらに、ルーティングによってURLの履歴が管理され、ブラウザの戻る・進むボタンが適切に動作するようになります。Reactではルーティングの実装にReact Routerが広く利用されています。

React Routerとは

React RouterはReactで複数のページを持つアプリケーション構築に利用されるライブラリです。同様の機能を持つライブラリとして他にもReach RouterWouterNaviRouter5といったものが存在していますが、React Roterが広く採用されるのは以下のような理由からです。

  • Reactアプリケーションの開発者にとって学習コストが低いため、スムーズに導入できる
  • ネストされたルート、動的ルート等、ルーティングのための高度な機能も提供されている
  • 多くの開発者が利用しており、コミュニティからのサポートが豊富に存在する
  • セットアップ容易である

React RouterはURLとコンポーネントを紐づける仕組みを提供します。例えば、ブラウザから"/signin"のURLにアクセスするとサインイン用のコンポーネント、"/contact"のURLにアクセスすると問い合わせフォーム用のコンポーネント表示させるといったことがReact Routerのライブラリによって可能になります。

React Routerのインストール

ターミナルでReact Routerを使用するプロジェクトフォルダに移動し、以下のコマンドを入力することでReact Routerをインストールできます。

npm install react-router-dom

本教材に沿ってコード作成している場合には、"hello-in-japanese"のプロジェクトフォルダに移動しインストールを実行します。現在、React Routerの最新バージョンはバージョン6になります。よって、この教材ではバージョン6のReact Routerについて解説していきます(上記のコマンドを実行すると、自動的にバージョン6がインストールされます)。

ルーティングの設定

React Routerによるルーティングを確認するために、表示用コンポーネントを複数作成し、作成したコンポーネントにURLを対応させて、ブラウザからアクセスしてみましょう。srcフォルダの中にpagesフォルダを作成し、その中に表示用コンポーネントを保存します。ここでは表示用コンポーネントとして以下のHome.jsx、About.jsx、Contact.jsxを作成します。

Home.jsx
const Home = () => {

return <h2>Home</h2>;
                      
}
                      
export {Home}
About.jsx
const About = () => {

return <h2>About</h2>;
                      
}
                      
export {About}
Contact.jsx
const Contact = () => {

return <h2>Contact</h2>;
                      
}
                      
export {Contact}

表示用コンポーネントの準備が出来たので、React Routerによりルーティングできるようにsrcフォルダのindex.jsxを以下のように編集します。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { App } from './App';
import { BrowserRouter } from 'react-router-dom';
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);

上記のBrowserRouterの下の階層においてReact Routerによるルーティングの設定が可能になります。続いてsrcフォルダにあるApp.jsxファイルを以下のように編集します。これにより、Home, About, Contactそれぞれ表示用コンポーネントに対して"/"(ルート)、"/about"、"/contact"のURLを対応させています。

App.jsx
import { Routes, Route } from 'react-router-dom';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Contact } from './pages/Contact';
                      
const App = () => {
                      
  return (
    <div>
      <h1>React Routerを使って表示しています</h1>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
      </Routes>
    </div>
  );
                      
}
                      
export {App};

<Routes>と</Routes>の間にルーティングするコンポーネントの数に応じて<Route path="" element={}/>を記述し、path属性にURL、element属性に対応させる表示用コンポーネントを設定します。なお、<Routes>と</Routes>の外にあるh1タグの内容はURLに関わらず常時表示されます。このようにページ遷移に関わらず表示したいコンポーネント(ヘッダ、サイドバー等)は<Routes>と</Routes>の外側に記述します。上記で設定したURLにブラウザでアクセスした表示結果が以下になります。

2023-02-19-19-50-54_.png "/"(ルート)のURLで、Homeコンポーネントの内容が表示されています。

2023-02-19-19-51-19_.png "/about"のURLで、Aboutコンポーネントの内容が表示されています。

2023-02-19-19-52-00_.png "/contact"のURLで、Contactコンポーネントの内容が表示されています。

URLにアクセスすることで、対応するコンポーネントの内容が表示されていることが確認できます。ここで、設定してないURLにアクセスした場合についても確認してみましょう。例えば、"/test"にアクセスすると以下の表示になります。

2023-02-19-20-17-35_.png "/test"のURLでは何も表示されません。

このように設定していないURLにアクセスしても何も表示されません。設定していないURLに対して同じ内容のページを表示したいこともあります。設定していないURLに対する表示用としてNoMatchというコンポーネントを作成し、表示するよう設定してみましょう。pagesフォルダに以下に示すNoMatch.jsxを作成しましょう。

NoMatch.jsx
const NoMatch = () => {

  return <h2>The requested URL was not found.</h2>;
                      
}
                      
export {NoMatch}

作成出来たらApp.jsxファイルにNoMatchコンポーネントをimportし、<Routes>と</Routes>の間に"<Route path="*" element={<NoMatch />}>"を追加しましょう。以下のようになるはずです。

App.jsx
import { Routes, Route } from 'react-router-dom';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Contact } from './pages/Contact';
import { NoMatch } from './pages/NoMatch';
                      
const App = () => {
                      
  return (
    <div>
      <h1>React Routerを使って表示しています</h1>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<NoMatch />} />
      </Routes>
    </div>
  );
                      
}
                      
export {App};

Routeタグのpath属性に*を指定すると、他のRouteタグでpath属性に指定していないURLにアクセスしたときの移動先になります。再び"/test"にアクセスすると以下の表示になることが確認できます。

2023-02-19-20-47-05_.png "/test"のURLで、NoMatchコンポーネントの内容が表示されています。

以上がルーティングの設定についての説明になりますが、ルーティングさせるコンポーネントにPropsを渡す方法についても確認しましょう。例えは、Appコンポーネントでメッセージのスタイルを指定するオブジェクトを定義し、NoMatchコンポーネントへPropsとして渡して、NoMatchコンポーネントのメッセージのスタイルを変更したいとします。そのような場合、App.jsxとNoMatch.jsxをそれぞれ以下のように編集します。

App.jsx
import { Routes, Route } from 'react-router-dom';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Contact } from './pages/Contact';
import { NoMatch } from './pages/NoMatch';
                      
const App = () => {

  const messageStyle = {color:  "red", fontSize: "50pt"};
                      
  return (
    <div>
      <h1>React Routerを使って表示しています</h1>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<NoMatch style={messageStyle} />} />
      </Routes>
    </div>
  );
                      
}
                      
export {App};
NoMatch.jsx
const NoMatch = ({style}) => {

  return <h2 style={style} >The requested URL was not found.</h2>;
                      
}
                      
export {NoMatch}

上記のApp.jsxを見てください。変更箇所は"const messageStyle = {color: "red", fontSize: "50pt"}"と"<Route path="*" element={<NoMatch style={messageStyle} />}>"になります。<NoMatch />の属性としてstyle={messageStyle}を渡すことで、受け取り側のNoMatch関数において従来のPropと同じように使用することができます。これが、ルーティングするコンポーネントへのPropsの渡し方になります。"/test"にアクセスしたときの表示は以下のようになります。

2023-02-19-21-12-43_.png NoMatchコンポーネントの表示メッセージのスタイルが変更されています。

NoMatchコンポーネントの表示にPropsで渡したスタイルが反映できていることが確認できます。

<Link to=””></Link>でリンク先を指定

ルーティングの設定が完了したので、設定したページのリンクをクリックしてページを移動できるようにしてみましょう。React Routerではリンクの作成にLinkコンポーネントを利用します。App.jsxを以下のように編集してみましょう。

App.jsx
import { Routes, Route, Link } from 'react-router-dom';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Contact } from './pages/Contact';
import { NoMatch } from './pages/NoMatch';
                      
const App = () => {
                      
  return (
    <div>
      <h1>React Routerを使って表示しています</h1>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
        <li>
          <Link to="/contact">Contact</Link>
        </li>
      </ul>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="*" element={<NoMatch/>} />
      </Routes>
    </div>
  );
                      
}
                      
export {App};

このように<Link to=””></Link>のto属性にリンク先のURL、開始タグと終了タグの間にリンクとなる文字を入力することでリンクを作成できます。ここで、react-router-domからLinkをimportするのを忘れないようにしてください。ブラウザでリンクが表示されていること、リンクのクリックにより指定したコンポーネントを表示できることが以下の写真のように確認できます。

2023-02-20-10-33-26_.png "/"(ルート)にアクセスすると、リンクが表示されます。

2023-02-20-10-34-46_.png "/"(ルート)で"About"のリンクをクリックすると、リンク先に設定したAboutのコンポーネントの内容が表示されます。

2023-02-20-10-36-44_.png "/about"で"Contact"のリンクをクリックすると、リンク先に設定したContactのコンポーネントの内容が表示されます。

2023-02-20-10-40-06_.png "/contact"で"Home"のリンクをクリックすると、リンク先に設定したHomeのコンポーネントの内容が表示されます。

ルーティングのネスト

特定のコンポーネントの内部で別の複数のコンポーネントのルーティングを行いたいとします。例えば以下の図のように、"/test"で表示されるコンポーネントから"/test/page1"や"/test/page2"に移動した時に、"/test"の表示部はそのままでその他の表示を切り替えるといったケースです。このような時、ルーティングのネストを使用します。

ルーティングのネストの説明.png "/test"のコンポーネントの表示部はそのままで他の表示を切り替えるようなルーティング

ルーティングのネストの実習として、"/products"というURLで表示されるコンポーネントから、"/products/fan"と"/products/heater"に移動した時に対応するコンポーネントを表示させるコードを記述してみましょう。まず、"/products/fan"および"/products/heater"で表示されるコンポーネントFan.jsxおよびHeater.jsxを以下のように作成します。

Fan.jsx
const Fan = () => {

  return <h3>Fan</h3>;
                    
}
                    
export {Fan}
Heater.jsx
const Heater = () => {

return <h3>Heater</h3>;

}

export {Heater}

続いて"/products"で表示されるProducts.jsxを以下のように作成します。

Products.jsx
import { Link } from 'react-router-dom';

const Products = () => {
                          
  return (
    <>
      <h2>Product lineup</h2>
      <ul>
        <li>
          <Link to="fan">Fan</Link>
        </li>
        <li>
          <Link to="heater">Heater</Link>
        </li>
      </ul>
    </>  
  );

}
                      
export {Products}

Products.jsxにおいて、Linkコンポーネントを使って"/products/fan"および"/products/heater"にルーティングできるようにしています。ここでLinkコンポーネントのto属性には、"/products"からの相対パスを指定します。上記の3ファイルも他の表示用コンポーネントと同じpagesフォルダに保存しましょう。さらにApp.jsxを以下のように編集します。

App.jsx
import { Routes, Route, Link } from 'react-router-dom';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Contact } from './pages/Contact';
import { NoMatch } from './pages/NoMatch';
import { Products } from './pages/Products';
import { Fan } from './pages/Fan';
import { Heater } from './pages/Heater';
                      
const App = () => {
                      
  return (
    <div>
      <h1>React Routerを使って表示しています</h1>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
        <li>
          <Link to="/contact">Contact</Link>
        </li>
        <li>
          <Link to="/products">Products</Link>
        </li>
      </ul>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/products" element={<Products />} >
          <Route path="fan" element={<Fan />} />
          <Route path="heater" element={<Heater />} />
        </Route>
        <Route path="*" element={<NoMatch/>} />
      </Routes>
    </div>
  );
                      
}
                      
export {App};

上記のように、Productsコンポーネント内部でルーティングしたいコンポーネントについては、それらをelement属性に持つRouteコンポーネントを<Route path="/products" element={<Products />} >と</Route>のタグの間に記述します。このとき、タグの間に記述したRouteコンポーネントのpath属性には親要素のpath属性に対する相対パスを記述します。この段階ではまだProductsコンポーネントからFanコンポーネントあるいはHeaterコンポーネントを表示することはできません。Products.jsxに関して更に以下のように編集する必要があります。

Products.jsx
import { Link, Outlet } from 'react-router-dom';

const Products = () => {
                          
  return (
    <>
      <h2>Product lineup</h2>
      <ul>
        <li>
          <Link to="fan">Fan</Link>
        </li>
        <li>
          <Link to="heater">Heater</Link>
        </li>
      </ul>
      <Outlet/>
    </>  
  );
}
                      
export {Products}

変更点は"react-router-dom"からOutletコンポーネントをimportし、FanコンポーネントあるいはHeaterコンポーネントを表示する場所に<Outlet/>を記述したことです。ブラウザで結果を確認してみましょう。最初の画像はProductsのリンクをクリックして、"/products"のページに遷移した時です。

2023-02-20-20-50-06_.png "/products"にアクセスすると、"Fan"とHeater"のリンクが表示されます。

次は、"/products"において、Fanのリンクをクリックして、"/products/fan"のページに遷移した時です。

2023-02-20-20-50-42_.png "/products/fan"にアクセスすると、"Fan"とHeater"のリンクに加え、Fanコンポーネントの内容が表示されます。

最後に、"/products"において、Heaterのリンクをクリックして、"/products/heater"のページに遷移した時です。

2023-02-20-20-51-04_.png "/products/heater"にアクセスすると、"Fan"とHeater"のリンクに加え、Heaterコンポーネントの内容が表示されます。

いずれもURLに指定したコンポーネントが表示されているのが確認できます。このように、ルーティングのネストを使用することで、特定のコンポーネント内でのページ遷移が可能になります。ここで"/products/fan"や"/products/heater"のページでは、FanとHeaterのリンクを表示したくないと考える方もいるかもしれません。そこで、これらのリンクは"/products"のページにおいてのみ表示するように変更してみます。まず、Products.jsxを以下のように編集しましょう。

Products.jsx
import { Outlet } from 'react-router-dom';

  const Products = () => {
                          
  return (
    <>
      <h2>Product lineup</h2>
      <Outlet/>
    </>  
  );

}
                      
export {Products}

変更点はLinkコンポーネントを取り除いたことです。続いて取り除いたリンクを表示するコンポーネントLineup.jsxを新たに作成し、pagesフォルダに保存しますます。内容は以下のようになります。

Lineup.jsx
import { Link } from 'react-router-dom';

const Lineup = () => {
                          
  return (
    <ul>
      <li>
        <Link to="fan">Fan</Link>
      </li>
      <li>
        <Link to="heater">Heater</Link>
      </li>
    </ul>
  );
}
                      
export {Lineup}

最後にApp.jsxを以下のように修正します。

App.jsx
import { Routes, Route, Link } from 'react-router-dom';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Contact } from './pages/Contact';
import { NoMatch } from './pages/NoMatch';
import { Products } from './pages/Products';
import { Fan } from './pages/Fan';
import { Heater } from './pages/Heater';
import { Lineup } from './pages/Lineup';
                      
const App = () => {
                      
  return (
    <div>
      <h1>React Routerを使って表示しています</h1>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
        <li>
          <Link to="/contact">Contact</Link>
        </li>
        <li>
          <Link to="/products">Products</Link>
        </li>
      </ul>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/products" element={<Products />} >
          <Route index element={<Lineup />} />
          <Route path="fan" element={<Fan />} />
          <Route path="heater" element={<Heater />} />
        </Route>
        <Route path="*" element={<NoMatch/>} />
      </Routes>
    </div>
  );
                      
}
                      
export {App};

先ほど作成したLineupコンポーネントをimportし、<Route index element={<Lineup />} />を追記しています。ここでpath属性の代わりにindexという属性を指定しています。このindexを指定すると、その親要素のURL"/products"にアクセスしたときに、Productsコンポーネントの<Outlet/>に内容が出力されるようになります。結果をブラウザで確認してみましょう。まずは"/products"にアクセスしたときの画像を以下に示します。

2023-02-20-21-32-35_.png "/products"にアクセスすると、"Fan"とHeater"のリンクが表示されます。

続いて、"/products"のページでFanのリンクをクリックしたときの画像を以下に示します。

2023-02-20-21-33-41_.png "/products/fan"にアクセスすると、Fanコンポーネントの内容のみ表示されます。

最後に、"/products"のページでHeaterのリンクをクリックしたときの画像を以下に示します。

2023-02-20-21-34-25_.png "/products/heater"にアクセスすると、Heaterコンポーネントの内容のみ表示されます。

このようにRouteコンポーネントのindex属性を使用することで、"/products"のページでのみFanとHeaterのリンクを表示することが可能になりました。

動的なルーティング

異なるURLのページにおいて同じレイアウトで内容のみ変更したいことがよくあります。例えば以下の図のように、マイケルとジャンという2人の人物を紹介するようなページがあったとして、"/member/michael"ではマイケルの、"/member/jean"ではジャンの紹介が表示される時に、レイアウトは同じままで表示する名前や写真、その他に用意された項目の内容のみ変更されるようなケースです。このような時、URLの末尾が"michael"か"jean"かによって内容を出し分けることが可能です。これを動的なルーティングといいます。

動的なルーティングの説明.png 共通のレイアウトで内容のみ変更される例

動的なルーティングの方法を実際にコードを記述して学習しましょう。先ほどは、"/products/fan"および"/products/heater"にアクセスするとそれぞれFanコンポーネントおよびHeaterコンポーネントの内容を表示するコードを記述しました。次は、製品の表示レイアウトのみ定義するProductコンポーネント、FanおよびHeaterの製品スペックを記述したproductListオブジェクト、これらを各々記述したファイルを予め作成しておきます。そして"/products/fan"の場合にFanの製品スペックを"/products/heater"の場合にHeaterの製品スペックをProductコンポーネントで表示するようにしてみます。まず以下のproductListオブジェクトを記述したproductList.jsを作成し、pagesフォルダに保存しましょう。

productList.js
const productList = {
  fan : {
    name:"Fan",
    power:"5W",
    voltage:"12V",
    weight:"5kg"
  },
  heater : {
    name:"Heater",
    power:"80W",
    voltage:"120V",
    weight:"40kg"
  }
}
                  
export {productList};

ここでは、FanおよびHeaterの製品スペックをそれぞれproductListオブジェクトのfanプロパティおよびheaterプロパティとして定義しています。productListオブジェクトはこの後作成するProduct.jsxで使用するのでexportしています。続いて、以下に示すProduct.jsxを作成し、pagesフォルダに保存しましょう。

Product.jsx
import { productList } from "./productList"

const Product = () => {
                      
  return (
    <>
      <h3></h3>
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </>
  );

}
                      
export {Product}

ここで、先ほど作成したproductListオブジェクトをimportしています。return文では製品スペックの表示用のタグのみ記述しています。h3タグでは製品名を、その下のliタグで製品スペックを箇条書きで記述するようレイアウトしています。ここで、"/products/ . . . "にアクセスしたときに、Productコンポーネントの内容を表示できるようにApp.jsxを以下のように編集します。

App.jsx
import { Routes, Route, Link } from 'react-router-dom';
import { Home } from './pages/Home';
import { About } from './pages/About';
import { Contact } from './pages/Contact';
import { NoMatch } from './pages/NoMatch';
import { Products } from './pages/Products';
import { Lineup } from './pages/Lineup';
import { Product } from './pages/Product';
                      
const App = () => {
                      
  return (
    <div>
      <h1>React Routerを使って表示しています</h1>
      <ul>
        <li>
          <Link to="/">Home</Link>
        </li>
        <li>
          <Link to="/about">About</Link>
        </li>
        <li>
          <Link to="/contact">Contact</Link>
        </li>
        <li>
          <Link to="/products">Products</Link>
        </li>
      </ul>
      <Routes>
        <Route path="/" element={<Home />} />
        <Route path="/about" element={<About />} />
        <Route path="/contact" element={<Contact />} />
        <Route path="/products" element={<Products />} >
          <Route index element={<Lineup />} />
          <Route path=":productName" element={<Product />} />
        </Route>
        <Route path="*" element={<NoMatch/>} />
      </Routes>
    </div>
  );
                      
}
                      
export {App};

先ほど作成したProductコンポーネントをimportし、<Route path=":productName" element={<Product />} />を追加しました。FanおよびHeaterコンポーネントに関する記述はここでは不必要なので削除してます。path=":productName"と記述していますが、":"のあとにproductNameを記述することにより、後でproductNameを変数としてURLの"/products/ . . . "の" . . . "の部分を文字列として取得できます。ここではproductNameとしましたが、任意の名前で構いません。ここまで作成出来たら再度Product.jsxを開き、以下のように編集します。

Product.jsx
import { useParams } from 'react-router-dom';
import { productList } from "./productList"
                      
const Product = () => {
                      
  const {productName} = useParams();
                      
  return (
    <>
      <h3>{productList[productName].name}</h3>
      <ul>
        <li>Power:{productList[productName].power}</li>
        <li>Voltage:{productList[productName].voltage}</li>
        <li>Weight:{productList[productName].weight}</li>
      </ul>
    </>
  );
}
                      
export {Product}

"useParams"という関数を"react-router-dom"からimportしています。この関数を使用すると、URLの文字列を取得できます。実際にこの関数から文字列を取得しているのが、"const {productName} = useParams();"になります。useParams()は{productName:"URL末尾の文字列"}というオブジェクトを返却しますが、ここではオブジェクトの分割代入により直接productNameを取得しています。return文のh3およびli要素においては、"productList[productName]"の部分でproductListオブジェクトから該当する製品スペックを記述したオブジェクトを指定し、".name"、".power"、".voltage"、".weight"の部分で各スペックのプロパティにアクセスしデータを取得しています。ブラウザで表示を確認してみましょう。

2023-02-21-12-06-53_.png "/products/fan"にアクセスすると、Fanのスペックが表示されます。

2023-02-21-12-07-28_.png "/products/heater"にアクセスすると、Heaterのスペックが表示されます。

このように、動的なルーティングを使用することによって、URLの文字列を使用して表示内容を変更するといったことも可能となります。

Lesson 3 Chapter 8
条件付きレンダリング

動的なWebアプリケーションを構築する場合、条件に応じて表示を切り替える設計が所々で必要となります。Reactでは条件に応じて表示するコンポーネントを簡単に変更できる方法が存在します。本チャプターではその方法を解説します

JSX内ではif文が記述できない

条件に応じて表示するコンポーネントを変更するとき時、プログラミングの経験がある方はif文の使用を検討するでしょう。実際にif文でコンポーネントを出し分けられるか確認してみましょう。index.jsxおよびApp.jsxを以下のように編集して実行してみてください。

index.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import {App} from './App.jsx'
                      
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>
);
App.jsx
const App = () => {

  const isJapan = true;
                  
  return (
    if(isJapan) {
      <p>こんにちは!</p>
    } else {
      <p>Hello !</p>
    }
  );
                  
}
                  
export {App};

上記のコードを実行すると以下のようにエラーが発生します。

2023-02-21-15-33-34_.png return文でifを使用するとエラーになります。

このようにJSX内では、普通の方法でif文を使用することができません。もしif文を使用するなら以下のようにreturn文ごと分岐させる必要があります。

App.jsx
const App = () => {

  const isJapan = true;
                  
  if(isJapan) {
    return <p>こんにちは!</p>
  } else {
    return <p>Hello !</p>
  }
                  
}
                  
export {App};

このようにreturn文ごと分岐させれば以下のように「こんにちは!」を表示させることができます。

2023-02-21-15-47-29_.png return文ごと分岐させると、「こんにちは!」が表示されます。

しかし長い表示の一部のみ場合分けしたい場合に上記の方法では共通した表示部が大部分であるにもかかわらずreturn文ごと分岐させることになり、無駄にコードの記述量が多くなってしまいます。以降ではこのような条件付きレンダリングを実行するための便利な方法を紹介します。

即時関数

無名関数を定義と同時に実行する関数を即時関数といいます。(( . . . )=>{ . . . })()のように2つ並べた括弧の最初の方に無名関数を定義することで、即時関数となります。App.jsxを即時関数を使用して表示するよう編集しながら、JSX内での即時関数の使い方を学んでいきましょう。先ほどのApp.jsxを即時関数を使用して書き換えると以下になります。

App.jsx
const App = () => {

  const isJapan = true;
                    
  return(
    (() => {
      if (isJapan) {
        return <p>こんにちは!</p>
      } else {
        return <p>Hello !</p>
      }
    })()
  );
                    
}
                    
export {App};

複数行になっているため分かりにくいですが、return文の中に即時関数を定義してます。実行する関数は"isJapan"がtrueなら<p>こんにちは!</p>、falseなら<p>Hello !</p>を返す関数となっています。ブラウザで表示すると、先ほどと同じ結果を確認できます。

三項演算子

即時関数も多用するとコードが見にくくなります。条件付きレンダリングをよりシンプルに実行する方法として、三項演算子を利用する方法があります。記述方法は以下のようになります。

condition ? condition===trueの時の処理 : condition===falseの時の処理

三項演算子を使用して、App.jsxを書き換えると以下のようになります。

App.jsx
const App = () => {

  const isJapan = true;
                    
  return <p>{isJapan ? "こんにちは!" : "Hello !"}</p>;
                    
}
                    
export {App};

三項演算子を使用することでコードがより簡潔になります。

論理積演算子

論理積演算子は"&&"のことです。使い方は以下のようになります。

condition && expresson

上記において、conditionがtrueならexpresson、falseならfalseを返します。falseは真偽値ですが、JSX内では真偽値が返されても何も表示されません。このことから、conditionがtrueの時のみ表示するということが可能になります。App.jsxをisJapanがtrueの時のみ"こんにちは!"を表示するように書き換えると次のようになります。

App.jsx
const App = () => {

  const isJapan = true;
                    
  return isJapan && <p>こんにちは!</p>
                    
}
                    
export {App};

isJapanがtrueであれば表示結果はこれまでと同じになりますが、falseの時は何も表示されなくなります。

Lesson 3 Chapter 9
配列データのレンダリング

Reactでは配列の各要素に対して、同じパターンでレンダリングする処理がよく行われます。本チャプターでは、配列の処理に有効なmap()関数の使用方法とレンダリングへの応用について解説します。

map() 関数での配列処理

配列に格納した文字列をコンソールに順に表示したいとき、通常はfor文を使用して配列要素をインデックスで指定して取り出した後にコンソールに表示する処理を行います。この処理を配列要素の数だけ繰り返します。その具体例が以下になります。

const fruits = ["りんご", "バナナ", "みかん"];

for (let index = 0; index 

この程度なら問題ないですが、同じような処理をする配列が多数あるとfor文が多用されコードが複雑になってしまいます。このように配列の要素を順に処理したい場合に役に立つのがmap() 関数です。配列名.map(配列要素に対して実行する関数)という記述によって配列要素が順に引数となり、map()に記述した関数が実行されます。関数の実行結果も配列で返されます。先ほどのコードをmap関数を使って書き換えると以下のようになります。

const fruits = ["りんご", "バナナ", "みかん"];

fruits.map((fruit,index)=>{console.log(`${index}:${fruit}`)});

このようにmap関数を使用すると配列の繰り返し処理をシンプルに記述できます。map()の中の関数にはfruitおよびindexの2つの引数が設定されていますが、これらはそれぞれ配列要素および参照している配列要素のインデックスとなります。これらの引数名は他の名前にすることも可能です。またindexは使用しない場合、省略することもできます。これらの実行結果はコンソール上で以下の表示となります。

0:りんご
1:バナナ
2:みかん

map関数を応用することで配列の要素を順にレンダリングすることが可能となります。試しにfruits配列の要素を順にブラウザに表示してみましょう。App.jsxを以下のように編集します。

App.jsx
const App = () => {

  const fruits = ["りんご", "バナナ", "みかん"];

  return(
    <ul>
      {fruits.map((fruit) => {
        return <li>{fruit}</li>
      })}
    </ul>
  );
                  
}
                  
export {App};

編集後にプログラムを実行すると、以下のように配列要素が順に箇条書きで表示されているのが確認できます。

2023-02-21-20-49-01_.png fruits配列の要素が箇条書きで表示されています。

keyの指定方法

先ほどmap関数を使用して配列要素をレンダリングする方法を説明しましたが、この状態だとコンソール上で以下のような警告が表示されてしまいます。

Warning: Each child in a list should have a unique "key" prop.

"リスト項目にはユニークなkeyを与えるべきだ"という内容です。Reactの公式サイトによれば、レンダリングしている配列に対して変更、追加あるいは削除等の操作を行ったとき、どの要素に操作が行われたのかをReactが識別するのにkeyが使われるので付与することを推奨しています。また、パフォーマンスへの悪影響、コンポーネントの状態の異常を起こす可能性があることからkeyとして配列のindexを使用することは推奨されていません。

先ほどのApp.jsxで配列要素をレンダリングするliタグにkeyを付与して、警告が出なくなることを確認します。keyは配列内でユニークであればよい(重複しなければよい)ので配列要素をそのまま使用することにします。これらを踏まえて編集したApp.jsxが以下になります。

App.jsx
const App = () => {

  const fruits = ["りんご", "バナナ", "みかん"];
                    
  return(
    <ul>
      {fruits.map((fruit) => {
        return <li key = {fruit} >{fruit}</li>
      })}
    </ul>
  )
                    
}
                    
export {App};

上記の編集後にプログラムを実行すると、ブラウザの表示は変わりませんが、コンソール上の表示は消えることが確認できます。このようにmap関数で配列要素をレンダリングするときは要素を表示するタグにユニークなkeyを付与することを忘れないようにしましょう。