1. はじめに
デザイン事業部の長谷です。
今回は、「天気検索アプリの作成を通し、ReactやAPIへの理解を深めよう」の後編になります!
前編では、Viteを使用した開発環境の準備〜OpenWeatherMapのAPIキー取得、Reactの組み込みHooksを使用したデータ取得の実装について学びました。
先に前編をご覧いただいた方が、後編の内容がスムーズに入るかと思うので
まだご覧になられていない方は、前編もご覧下さい!
後編では、取得した天気データを表示するためのUIをMaterial-UI(MUI)とBulmaを使用し、簡単に作成していきます。
また今回アプリを作成しているのは、あくまでReactやAPIへの理解を深めるためになります。従って、「素早く・簡単」に作成するUIになりますので、ご容赦ください。
2. Material-UI(MUI)の導入
Material-UI、通称MUIは、Reactのためのデザインコンポーネントライブラリです。
複雑なUIを実現する場合には、難点が出てくるかもしれませんが、今回は「素早く・簡単」にが最重要ですので、その点MUIは優れていると思います。
以下が今回の手順になります。
1.MUIをインストールします。
(今回はMUIのアイコンのみ使用する為、この手順は飛ばしても、実装はできると思います)
1 | npm install @mui/material @emotion/react @emotion/styled |
2.MUIのアイコンをインストールします。
1 | npm install @mui/icons-material |
3.MUIのコンポーネントを使用し、天気アイコンを表示します。App.jsx
の中に以下のように、記述します。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 | import React, { useState, useEffect } from 'react'; // 以下文で使用するMUIのアイコンをインポート import { Search, WbSunny, WbCloudy, Opacity, ThunderstormTwoTone, GrainTwoTone } from '@mui/icons-material'; const WeatherApp = () => { // 状態管理用のuseState const [city, setCity] = useState(''); // 都市名が変更された際に天気データを取得するためのuseEffect useEffect(() => {}); // 天気状態に応じたアイコンを返す関数 const getWeatherIcon = (weatherCondition) => { const iconMap = { Clear: <WbSunny fontSize="large" />, Clouds: <WbCloudy fontSize="large" />, Rain: <Opacity fontSize="large" />, Drizzle: <GrainTwoTone fontSize="large" />, Thunderstorm: <ThunderstormTwoTone fontSize="large" />, }; return iconMap[weatherCondition] || null; }; return ( <div className="container"> {/* 検索バー */} <div className="is-flex is-justify-content-center is-align-items-center mb-2"> <Search fontSize="medium" /> <h1 className="title is-6 pr-2">地域検索</h1> </div> {/* 天気情報 */} {weatherData && ( <div className="box"> <div> {getWeatherIcon(weatherData.weather[0].main)} <p className="heading is-size-7">{weatherData.weather[0].description}</p> </div> </div> )} </div> ); }; export default WeatherApp; |
上記のコードでは、weatherCondition
に応じて、適切な天気アイコンを表示しています。
3. Bulmaの導入
MUIのアイコンを無事使用できるようになったので、次はスタイルを当てていきます。
今回は「素早く・簡単」を求めるため、Bulmaを使用していきます。
ここで、Bulmaについて軽く説明します。
Bulmaとは、シンプルで使いやすいCSSフレームワークです。他の同種で例を挙げると、BootstrapやTailwind CSSがあります。これらのCSSフレームワークは基本的に、「読み込み・定義されているクラス名を付与」という一連の手段をとることで、クラス名に紐づいているスタイルを付与できます。
それぞれに特徴があり、Reactとの相性を考えると、実務で使用するのであれば、ReactとTailwind CSSの組み合わせがベストな気がします。しかし、今回は見栄えが悪くなりすぎない最低限のUIを実装できればいいので、手軽に利用できるBulmaを使用して、スタイルを付与していきます。
以下が具体的な手順です。
1.Bulmaをインストールします。
1 | npm install bulma |
2.Bulmaのスタイルシートをインポートします。App.jsx
の中に、以下の文を記述します。
1 | import 'bulma/css/bulma.min.css'; |
3.Bulmaのクラスを使用し、天気情報を表示するコンポーネントを作成する。App.jsx
の中に、以下のように記述します。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 | import React from 'react'; import 'bulma/css/bulma.min.css'; const WeatherApp = () => { return ( <div className="container"> {/* 検索バー */} <div className="is-flex is-justify-content-center is-align-items-center mb-2"> <Search fontSize="medium" /> <h1 className="title is-6 pr-2">地域検索</h1> </div> <div className="field"> <div className="control"> <input className="input" type="text" placeholder="地域を入力" value={city} onChange={(e) => setCity(e.target.value)} /> </div> </div> {/* エラーメッセージ */} {error && <p className="help is-danger">{error}</p>} {/* 天気情報 */} {weatherData && ( <div className="box"> <h2 className="subtitle is-5 has-text-centered mb-2">{weatherData.name}</h2> <p className="is-size-6 has-text-centered mb-3">{formatDateTime(weatherData.forecast.list[0].dt_txt)}</p> <div className="level is-mobile mb-5"> <div className="level-item has-text-centered"> <div> {getWeatherIcon(weatherData.weather[0].main)} <p className="heading is-size-7">{weatherData.weather[0].description}</p> </div> </div> <div className="level-item has-text-centered"> <p className="title is-4">{weatherData.main.temp}<span className="is-size-6">°C</span></p> </div> </div> <div className="level is-mobile"> <div className="level-item has-text-centered"> <div> <p className="title is-6"> <span className="has-text-danger">{weatherData.forecast.list[0].main.temp_max}°C</span> / <span className="has-text-info">{weatherData.forecast.list[0].main.temp_min}°C</span> </p> </div> </div> <div className="level-item has-text-centered"> <div> <p className="title is-6 is-flex is-align-items-center"> <Opacity fontSize="small" /> {weatherData.forecast.list[0].pop * 100}% </p> </div> </div> </div> </div> )} </div> ); } export default WeatherInfo; |
上記のコードでは、Bulmaのクラスを使用して、都市名、天気アイコン、気温、天気の説明を表示しています。
4. コンポーネントの実装
最後に、検索バーと天気情報を表示するコンポーネントを組み合わせて、アプリを完成させます。
以下が最終的なApp.jsx
のコードになります。
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | import React, { useState, useEffect } from 'react'; import axios from 'axios'; import { Search, WbSunny, WbCloudy, Opacity, ThunderstormTwoTone, GrainTwoTone } from '@mui/icons-material'; import 'bulma/css/bulma.min.css'; const API_KEY = 'MY_API_KEY'; const BASE_URL = 'https://api.openweathermap.org/data/2.5/weather'; const FORECAST_URL = 'https://api.openweathermap.org/data/2.5/forecast'; const WeatherApp = () => { // 状態管理用のuseState const [city, setCity] = useState(''); const [weatherData, setWeatherData] = useState(null); const [error, setError] = useState(null); // 都市名が変更された際に天気データを取得するためのuseEffect useEffect(() => { const fetchData = async () => { // 都市名が空の場合は処理を終了 if (!city) return; try { // 現在の天気データと予報データを並行して取得 const [weatherResponse, forecastResponse] = await Promise.all([ axios.get(BASE_URL, { params: { q: city, appid: API_KEY, units: 'metric', lang: 'ja' } }), axios.get(FORECAST_URL, { params: { q: city, appid: API_KEY, units: 'metric', lang: 'ja' } }), ]); // 取得したデータを組み合わせてweatherDataに設定 setWeatherData({ ...weatherResponse.data, forecast: forecastResponse.data }); setError(null); } catch (error) { // エラーが発生した場合はweatherDataをnullに設定し、エラーメッセージを表示 setWeatherData(null); setError('都市名を正しく入力して下さい。'); } }; fetchData(); }, [city]); // 天気状態に応じたアイコンを返す関数 const getWeatherIcon = (weatherCondition) => { const iconMap = { Clear: <WbSunny fontSize="large" />, Clouds: <WbCloudy fontSize="large" />, Rain: <Opacity fontSize="large" />, Drizzle: <GrainTwoTone fontSize="large" />, Thunderstorm: <ThunderstormTwoTone fontSize="large" />, }; return iconMap[weatherCondition] || null; }; // 日時データを日本語表記にフォーマットする関数 const formatDateTime = (dateTimeString) => { return new Date(dateTimeString).toLocaleString('ja-JP', { month: 'long', day: 'numeric' }); }; return ( <div className="container"> {/* 検索バー */} <div className="is-flex is-justify-content-center is-align-items-center mb-2"> <Search fontSize="medium" /> <h1 className="title is-6 pr-2">地域検索</h1> </div> <div className="field"> <div className="control"> <input className="input" type="text" placeholder="地域を入力" value={city} onChange={(e) => setCity(e.target.value)} /> </div> </div> {/* エラーメッセージ */} {error && <p className="help is-danger">{error}</p>} {/* 天気情報 */} {weatherData && ( <div className="box"> <h2 className="subtitle is-5 has-text-centered mb-2">{weatherData.name}</h2> <p className="is-size-6 has-text-centered mb-3">{formatDateTime(weatherData.forecast.list[0].dt_txt)}</p> <div className="level is-mobile mb-5"> <div className="level-item has-text-centered"> <div> {getWeatherIcon(weatherData.weather[0].main)} <p className="heading is-size-7">{weatherData.weather[0].description}</p> </div> </div> <div className="level-item has-text-centered"> <p className="title is-4">{weatherData.main.temp}<span className="is-size-6">°C</span></p> </div> </div> <div className="level is-mobile"> <div className="level-item has-text-centered"> <div> <p className="title is-6"> <span className="has-text-danger">{weatherData.forecast.list[0].main.temp_max}°C</span> / <span className="has-text-info">{weatherData.forecast.list[0].main.temp_min}°C</span> </p> </div> </div> <div className="level-item has-text-centered"> <div> <p className="title is-6 is-flex is-align-items-center"> <Opacity fontSize="small" /> {weatherData.forecast.list[0].pop * 100}% </p> </div> </div> </div> </div> )} </div> ); }; export default WeatherApp; |
上記のコードでは、検索バーと天気情報を表示するコンポーネントを組み合わせています。handleCityChange
関数で、入力された都市名をcity
の状態に設定し、useEffect
内でデータ取得を行っています。
また、App.jsx内に付与したBulmaのスタイルだけだと要素が左右中央に配置されないと思うので、既存のindex.css
ファイル内に申し訳程度で以下コードを書き足していただければ、完成です。index.css
の中にも、以下スタイルを追記します。
1 2 3 4 5 | /* 〜既存のコード〜 */ #root { margin-inline: auto; /* 「margin: 0 auto」でもOK */ } |
5. アプリの完成
これで、簡単な天気検索アプリが完成しました。アプリを起動すると、以下のような画面が表示されます。
実際の使用イメージは以下になります。
6. まとめ
前後編を含め、本記事ではReactとOpenWeatherMap APIを使用して、簡単な天気検索アプリを作成する方法を紹介しました。いかがでしたでしょうか?
個人的にViteを使用した開発環境の準備、APIキーの取得、データ取得の実装、MUIとBulmaを使用したUIデザインについて学ぶことができました。
今後も、React・APIを使用して、ウェブアプリケーション開発の理解を深めていこうと思います。
本記事が誰かの役に立てば幸いです。
読んでいただきありがとうございました!