0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ツーリングスポットアプリ React化【Part 2】

0
Posted at

コンポーネント分割・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.jsximport して <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 と書いても同じ意味ですが、分割代入の方がよく使われます。

どの状態をどこで管理するか

ルール:状態は「使う場所の一番近い共通の親」に置く

例えば selectedAreaFilterPanelMapArea の両方で使います。
どちらの親でもある App.jsx に置いて、props で渡すのが正解です。

App.jsx(selectedArea を useState で持つ)
├── FilterPanel   ← selectedArea を受け取る(表示・変更)
└── MapArea       ← selectedArea を受け取る(絞り込みに使う)

参考:props の名前の付け方

渡すもの 名前の例
表示する値 selectedAreaspots
値を変える関数 onAreaChangeonSearchonReset

📌 関数を渡すときは on〇〇 という名前にするのが慣習です。

やってみよう

  • FilterPanel.jsx を作り、絞り込みエリアの HTML を JSX に変換して移しましょう
  • App.jsx から selectedAreaonAreaChange などを props で渡しましょう
  • セレクトボックスや絞り込みボタンが動くことを確認しましょう

ステップ9|配列を JSX で表示しよう

map() で配列を JSX に変換する

Vanilla JS では forEachcreateElement していましたが、
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>
))}

やってみよう

  • filteredSpotsmap() でリスト表示してみましょう
  • 絞り込みボタンを押すと表示されるスポットが変わることを確認しましょう
  • 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 を作りましょう
  • LoadScriptGoogleMap を使って、地図だけ(ピンなし)を表示してみましょう
  • index.html<script src="https://maps.googleapis.com/..."> は削除して OK です

ステップ11|マーカー(ピン)を表示しよう

Marker コンポーネントを使おう

GoogleMap の中に Marker を書くと、ピンが表示されます。
filteredSpotsmap() でループして、スポットの数だけ 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 を受け取ろう

filteredSpotsApp.jsx で管理しているので、MapArea に props で渡します。

参考:MapArea に spots を渡す

// App.jsx
<MapArea spots={filteredSpots} />

// MapArea.jsx
function MapArea({ spots }) {
  // spots を map() でループする
}

やってみよう

  • App.jsx から filteredSpotsMapArea に props で渡しましょう
  • MapArea の中で spots.map() を使ってマーカーを表示しましょう
  • 絞り込みを実行すると地図のピンが変わることを確認しましょう

ステップ12|マーカーのホバー・クリックを実装しよう

イベントは props で渡す

Marker にもイベントを設定できます。
onMouseOveronClick という 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 を条件付きで表示しよう

activeSpotnull でないときだけ 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 を表示する」という書き方です。
activeSpotnull(= 偽)のときは何も表示されません。

やってみよう

  • MapArea.jsxactiveSpot という状態を作りましょう
  • マーカーにホバーしたとき setActiveSpot(spot) を呼びましょう
  • activeSpot && <InfoWindow>...</InfoWindow> で InfoWindow を表示しましょう
  • onCloseClicksetActiveSpot(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・プラン作成画面に進みます

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?