コンポーネント分割・props・Google マップ表示
このパートで学ぶこと
- コンポーネントに分割する考え方
propsで親から子にデータを渡す方法@react-google-maps/apiでマップを表示する方法- マーカーのクリック・ホバー処理を React で書く方法
ステップ7|コンポーネントとは何かを理解しよう
コンポーネント=画面の「部品」
React では、画面を コンポーネント という単位で分割します。
今まで index.html に全部書いていた HTML を、役割ごとにファイルに分けます。
今のアプリを分割するとこうなります
App
├── Header ← ヘッダー(タイトル・検索ボックス)
├── MapArea ← Google マップ + マーカー
├── FilterPanel ← 絞り込みサイドバー
├── PlanSection ← ツーリングプラン一覧
└── RecommendSection ← おすすめスポット
コンポーネントを作るルール
コンポーネントは 関数 で書きます。関数名は 大文字始まり にします。
参考:コンポーネントの書き方
// src/components/Header.jsx
function Header() {
return (
<header>
<div className="header-inner">
<h1 className="title">ツーリングスポットナビ</h1>
</div>
</header>
);
}
export default Header; // ← 必ず export する
参考:コンポーネントを別のファイルで使う書き方
// App.jsx
import Header from './components/Header';
function App() {
return (
<div>
<Header /> {/* ← タグとして使う */}
</div>
);
}
📌 コンポーネントは
<Header />のように タグとして書く ことで使えます。
自分で作ったコンポーネントは必ず 大文字始まり、HTML タグは小文字始まりです。
ファイル構成を整えよう
src/components/ フォルダを作り、その中にコンポーネントを置きます。
src/
├── components/
│ ├── Header.jsx
│ ├── MapArea.jsx
│ ├── FilterPanel.jsx
│ └── PlanSection.jsx
├── App.jsx
└── ...
やってみよう
-
src/components/フォルダを作りましょう - まず
Header.jsxを作り、index.htmlの<header>部分の JSX をそこに移しましょう -
App.jsxでimportして<Header />と書いて表示されることを確認しましょう
ステップ8|props でデータを渡そう
props とは?
コンポーネントに外から値を渡す仕組みが props です。
HTML の属性(src="..." や href="..." など)のようなイメージです。
参考:props を渡す書き方(親側)
// App.jsx
<FilterPanel
selectedArea={selectedArea}
onAreaChange={setSelectedArea}
/>
参考:props を受け取る書き方(子側)
// FilterPanel.jsx
function FilterPanel({ selectedArea, onAreaChange }) {
return (
<select
value={selectedArea}
onChange={e => onAreaChange(e.target.value)}
>
<option value="">選択してください</option>
{/* ... */}
</select>
);
}
📌
{ selectedArea, onAreaChange }のように{}で受け取るのは 分割代入 という書き方です。
props.selectedAreaと書いても同じ意味ですが、分割代入の方がよく使われます。
どの状態をどこで管理するか
ルール:状態は「使う場所の一番近い共通の親」に置く
例えば selectedArea は FilterPanel と MapArea の両方で使います。
どちらの親でもある App.jsx に置いて、props で渡すのが正解です。
App.jsx(selectedArea を useState で持つ)
├── FilterPanel ← selectedArea を受け取る(表示・変更)
└── MapArea ← selectedArea を受け取る(絞り込みに使う)
参考:props の名前の付け方
| 渡すもの | 名前の例 |
|---|---|
| 表示する値 |
selectedArea、spots
|
| 値を変える関数 |
onAreaChange、onSearch、onReset
|
📌 関数を渡すときは
on〇〇という名前にするのが慣習です。
やってみよう
-
FilterPanel.jsxを作り、絞り込みエリアの HTML を JSX に変換して移しましょう -
App.jsxからselectedArea・onAreaChangeなどを props で渡しましょう - セレクトボックスや絞り込みボタンが動くことを確認しましょう
ステップ9|配列を JSX で表示しよう
map() で配列を JSX に変換する
Vanilla JS では forEach で createElement していましたが、
React では map() を使って配列を JSX の要素に変換します。
参考:Vanilla JS と React の比較
// Vanilla JS
spots.forEach(spot => {
const p = document.createElement('p');
p.textContent = spot.name;
container.appendChild(p);
});
// React
{spots.map(spot => (
<p key={spot.name}>{spot.name}</p>
))}
📌
map()の中で返す要素には必ずkeyを付けます。
React が「どの要素が変わったか」を判断するために使います。
keyには 一意な値(重複しない値)を使います。
スポット一覧を表示してみよう
まず地図なしで、スポット名だけをリスト表示してみます。
参考:スポット一覧の表示
// App.jsx
{filteredSpots.map(spot => (
<div key={spot.name}>
<p>{spot.name}</p>
<p>{spot.description}</p>
</div>
))}
やってみよう
-
filteredSpotsをmap()でリスト表示してみましょう - 絞り込みボタンを押すと表示されるスポットが変わることを確認しましょう
-
keyを付け忘れると、ブラウザの開発者ツールに警告が出ます。確認してみましょう
ステップ10|Google マップ用ライブラリを導入しよう
@react-google-maps/api とは?
Google Maps を React で使いやすくしたライブラリです。
ターミナルでインストールします。
npm install @react-google-maps/api
使う主なコンポーネントを知ろう
| コンポーネント | 役割 |
|---|---|
LoadScript |
Google Maps の API を読み込む |
GoogleMap |
地図を表示する |
Marker |
ピンを表示する |
InfoWindow |
ポップアップを表示する |
📌 今まで
<script src="https://maps.googleapis.com/...">で読み込んでいた部分を
<LoadScript>が代わりに担当します。
MapArea.jsx を作ろう
// 使い方のイメージ(参考)
<LoadScript googleMapsApiKey="あなたのAPIキー">
<GoogleMap
mapContainerStyle={...} ← 地図の幅・高さ(必須)
center={...} ← 中心座標
zoom={...} ← ズームレベル
>
{/* マーカーや InfoWindow はここに書く */}
</GoogleMap>
</LoadScript>
参考:mapContainerStyle の書き方
const containerStyle = {
width: '100%',
height: '500px',
};
📌 JSX の中でスタイルを書くときは
style={{ }}のように 二重波括弧 で書きます。
外側の{}は「JSX の中に JavaScript を書く」、内側の{}は「オブジェクト」です。
やってみよう
-
npm install @react-google-maps/apiを実行しましょう -
src/components/MapArea.jsxを作りましょう -
LoadScriptとGoogleMapを使って、地図だけ(ピンなし)を表示してみましょう -
index.htmlの<script src="https://maps.googleapis.com/...">は削除して OK です
ステップ11|マーカー(ピン)を表示しよう
Marker コンポーネントを使おう
GoogleMap の中に Marker を書くと、ピンが表示されます。
filteredSpots を map() でループして、スポットの数だけ Marker を置きます。
参考:Marker の書き方
import { GoogleMap, LoadScript, Marker } from '@react-google-maps/api';
<GoogleMap ...>
{filteredSpots.map(spot => (
<Marker
key={spot.name}
position={{ lat: spot.lat, lng: spot.lng }}
title={spot.name}
/>
))}
</GoogleMap>
props で filteredSpots を受け取ろう
filteredSpots は App.jsx で管理しているので、MapArea に props で渡します。
参考:MapArea に spots を渡す
// App.jsx
<MapArea spots={filteredSpots} />
// MapArea.jsx
function MapArea({ spots }) {
// spots を map() でループする
}
やってみよう
-
App.jsxからfilteredSpotsをMapAreaに props で渡しましょう -
MapAreaの中でspots.map()を使ってマーカーを表示しましょう - 絞り込みを実行すると地図のピンが変わることを確認しましょう
ステップ12|マーカーのホバー・クリックを実装しよう
イベントは props で渡す
Marker にもイベントを設定できます。
onMouseOver、onClick という props を使います。
参考:Marker のイベント
<Marker
key={spot.name}
position={{ lat: spot.lat, lng: spot.lng }}
title={spot.name}
onMouseOver={() => {
// ホバーしたとき
}}
onClick={() => {
// クリックしたとき
}}
/>
「選択中のスポット」を useState で管理しよう
どのスポットの InfoWindow を開くかを状態で管理します。
参考:選択中スポットの状態
// MapArea.jsx の中
const [activeSpot, setActiveSpot] = useState(null);
// null のとき → InfoWindow を表示しない
// スポットが入っているとき → そのスポットの InfoWindow を表示する
参考:ホバー時に activeSpot を更新する
<Marker
onMouseOver={() => setActiveSpot(spot)}
// ...
/>
InfoWindow を条件付きで表示しよう
activeSpot が null でないときだけ InfoWindow を表示します。
React では 条件付きレンダリング という書き方を使います。
参考:条件付きレンダリング
// && を使う書き方(よく使われる)
{activeSpot && (
<InfoWindow
position={{ lat: activeSpot.lat, lng: activeSpot.lng }}
onCloseClick={() => setActiveSpot(null)}
>
<div>
<p>{activeSpot.name}</p>
<p>{activeSpot.description}</p>
</div>
</InfoWindow>
)}
📌
A && Bは「A が真のとき B を表示する」という書き方です。
activeSpotがnull(= 偽)のときは何も表示されません。
やってみよう
-
MapArea.jsxにactiveSpotという状態を作りましょう - マーカーにホバーしたとき
setActiveSpot(spot)を呼びましょう -
activeSpot && <InfoWindow>...</InfoWindow>で InfoWindow を表示しましょう -
onCloseClickでsetActiveSpot(null)を呼んで閉じられることを確認しましょう
Part 2 まとめ
このパートで追加したファイルを整理します。
src/
├── components/
│ ├── Header.jsx ← ヘッダー
│ ├── MapArea.jsx ← 地図 + マーカー + InfoWindow(activeSpot を useState で管理)
│ └── FilterPanel.jsx ← 絞り込みサイドバー(selectedArea などを props で受け取る)
├── data/
│ └── spots.js
├── utils/
│ └── filterSpots.js
├── App.jsx ← 全体の状態管理(selectedArea、filteredSpots など)
└── index.css
学んだこと
- コンポーネントを作り・使う方法(
export default/import) -
propsで親から子にデータ・関数を渡す方法 -
map()で配列を JSX に変換する方法(keyが必要) -
@react-google-maps/apiでマップ・マーカーを表示する方法 -
&&を使った条件付きレンダリング
→ Part 3 では、LocalStorage・React Router・プラン作成画面に進みます