はじめに
学習するに至った経緯
2020年より、未経験からエンジニアへの転職を目指し、某プログラミングスクールへ通う。
入学後、『Ruby』を未経験から学ぶ人が多いのと『Ruby』の求人が思っていた以上に少ないので、
卒業後、フロントエンドのエンジニアを目指す事に。
Reactの学習した事を言語化し、認識の深化による備忘録として記載。
【作成にあたり学習した事】
- アプリをブートストラップする create-react-app
- Reactを使用する
- Reactフック
- 基本的なスタイリング
1. Reactアプリの作成方法
ターミナルでreactアプリケーションを作成。
npx create-react-app <Your app name>と入力。
例: npx create-react-app my-weather-app
パッケージのインストールが完了したら、プロジェクトフォルダに入ってnpm startと入力し、
デフォルトのReactテンプレートが表示を確認する。
npm start
app.jsファイルで、divタグ内のものをすべて消去。ロゴのインポートを削除。
2. 必要なパッケージのインストール方法
このアプリケーションをより魅力的にするために、いくつかの外部パッケージを使用する。
① Semantic React UIライブラリを使用。
これをインストールするには、ターミナルで以下のコマンドを入力する。
npm install semantic-ui-react semantic-ui-css
インストールが完了したら、index.jsを開き、ライブラリをインポートする。
以下のコマンドをコピーして、index.jsファイルに貼り付ける。
import 'semantic-ui-css/semantic.min.css'
時間をフォーマットするために、moment.jsを使用する。
以下のコマンドでインストールする。
npm install moment -save
インストールされている全てのパッケージをpackage.jsonファイルで確認する。
{
"name": "weather_app_",
"version": "0.1.0",
"private": true,
"dependencies": {
"@testing-library/jest-dom": "^5.11.4",
"@testing-library/react": "^11.1.0",
"@testing-library/user-event": "^12.1.10",
"@types/jest": "^26.0.15",
"@types/node": "^12.0.0",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"semantic-ui-css": "^4.0.3",
"semantic-ui-css": "^2.0.3",
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
},
3. お天気アプリの作り方
天気予報アプリケーションを動作させるには、OpenWeatherMapというサードパーティ製のAPIを使用。
下記のサイトにアクセスし、自分のアカウントを作成する。
その後、ナビゲーションバーにあるAPIオプションをクリックすると、「現在の天気データ」「1時間ごとの4時間予報」「16日間の予報」など、さまざまなオプションが表示される。これらは、データを取得するために必要なAPIエンドポイントです。また、これらのAPIを呼び出すには、APIキーが必要で、APIキーを取得するには、右上のユーザー名をクリックして、「my API keys」をクリックする。
まだ、存在していない場合は、APIキーを作成する。アプリのメインフォルダに、.envというファイルを作成する。これは環境変数ファイルで、すべてのAPIエンドポイントとキーが含まれる。
react_app_api_url = 'https://api.openweathermap.org/data/2.5'
REACT_APP_API_KEY = あなたのAPIキーをここに貼り付けます。
REACT_APP_ICON_URL = 'https://openweathermap.org/img/w'
REACT_APP_API_KEY変数に、コピーしたAPIキーを貼り付けます。
4. React Hooksの使い方
React Hooksを使うと、機能的なコンポーネントで状態を使用・管理することができる。
ここでは、useStateフックとuseEffectフックを使用。
import React, { useEffect, useState } from "react";
2つの状態を作成。1つは緯度用、もう1つは経度用。
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
次に、useEffect関数を作成する。この関数の目的は、アプリケーションがロードおよびリロードされたときに関数をロードすること。
useEffect(() => {
navigator.geolocation.getCurrentPosition(function(position) {)
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
console.log("Latitude is:", lat)
console.log("Longitude is:", long)
}, [lat, long])
navigator.geolocationを使って緯度と経度を取得し、setLongとsetLatを使って経度と緯度の状態を設定する。
import './App.css';
import React, { useEffect, useState } from "react";
export default function App() {
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
useEffect(() => {
navigator.geolocation.getCurrentPosition(function(position) {)
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
console.log("Latitude is:", lat)
console.log("Longitude is:", long)
}, [lat, long]);
return (
<div className="App">
</div>
);
}
これが現在のapp.jsファイルの姿で、コンソールで緯度と経度の値を確認することができる。
Our latitude and longitude
Latitude is: 25.5922166
Longitude is: 85.12761069999999
5.緯度と経度を使って現在地を取得する方法
緯度と経度に基づいてWeather APIから天気データを取得する別の関数getWeatherを作成する。
この関数では、APIからデータを取得するためにフェッチコールを使用。process.env.REACT_APP_API_URLはAPIアドレスを、
process.env.REACT_APP_API_KEYは.envファイルからAPIキーを取得。
※ latとlongは、前回取得した緯度と経度。
そして、そのデータをJSON形式に変換する。
次のステップでは、setDataを使って、結果をデータオブジェクトに格納する。
そのデータをコンソールに記録する。
await fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => res.json())
.then(result => {
setData(result)
console.log(result);
});
ここでは、緯度と経度に基づいたすべての気象データを見ることができる。
下記のファイルは、経度と緯度に基づいて天気データを取得する、新しいapp.jsファイル。
import './App.css';
import React, { useEffect, useState } from "react";
import Weather from './components/weather';
export default function App() {
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
navigator.geolocation.getCurrentPosition(function(position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
await fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => res.json())
.then(result => {
setData(result)
console.log(result);
});
}
fetchData();
}, [lat,long])
return (
<div className="App">
</div>
);
}
6. 天気予報コンポーネントの作成方法
天気データを表示するための天気コンポーネントを作成。srcフォルダ内にcomponentsというフォルダを作成し、その中にweather.jsというファイルを作成し、メインのapp.jsファイルでウェザーコンポーネントを呼び出す。
import './App.css';
import React, { useEffect, useState } from "react";
import Weather from './components/weather';
export default function App() {
const [lat, setLat] = useState([]);
const [long, setLong] = useState([]);
const [data, setData] = useState([]);
useEffect(() => {
const fetchData = async () => {
navigator.geolocation.getCurrentPosition(function(position) {
setLat(position.coords.latitude);
setLong(position.coords.longitude);
});
await fetch(`${process.env.REACT_APP_API_URL}/weather/?lat=${lat}&lon=${long}&units=metric&APPID=${process.env.REACT_APP_API_KEY}`)
.then(res => res.json())
.then(result => {
setData(result)
console.log(result);
});
}
fetchData();
}, [lat,long])
return (
<div className="App">
{(typeof data.main != 'undefined') ? (
<Weather weatherData={data}/>
): (
<div></div>
)}
</div>
);
}
return文にチェックを入れているのは、取得しているデータの種類が未定義の場合は、空のdivを表示する。また、fetch dataは非同期関数なので、このチェックを入れるのは必須。他の全ての関数の実行が終わった後に、この関数をロードする。そのため、このチェックを外すと、エラーが発生する。
これは、APIの呼び出しが行われる前に、アプリケーションがreturn文をレンダリングしてしまい、
その場合は表示するものがないため、未定義のエラーを投げてしまうためである。
7. ウェザーボディの作成方法
このパートでは、Semantic UIライブラリを使ってインターフェースをデザインする。
お天気情報を表示するカードを作成する。
import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react'
const CardExampleCard = ({weatherData}) => (
<Card>
<Card.Content>
<Card.Header className="header">{weatherData.name}</Card.Header>
</Card.Content>
</Card>
)
export default CardExampleCard;
ここでは、semantic-ui-reactからカードをインポートし、そのカードの中に、都市名を表示するヘッダーを入れているが、問題は、どうやってapp.jsからweather.jsコンポーネントにデータを取り込むのかということである。答えは、Reactではpropsを使って、親コンポーネントから子コンポーネントにデータを送ることができる。今回のケースでは、親コンポーネントがapp.jsで、子コンポーネントがweather.jsである。
そのためには、app.jsのコンポーネントにpropsを追加するだけである。
<Weather weatherData={data}/>
ここでは、weatherDataというプロップス名でデータを渡して、Weather.jsでweatherDataのプロップスを受け取る。
import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react'
const CardExampleCard = ({weatherData}) => (
<Card>
<Card.Content>
<Card.Header className="header">{weatherData.name}</Card.Header>
</Card.Content>
</Card>
)
export default CardExampleCard;
場所に応じて都市名を取得しているのがわかるのと同様に、天気予報コンポーネントにさらにフィールドを追加することもできる。
import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react'
const CardExampleCard = ({weatherData}) => (
<Card>
<Card.Content>
<Card.Header className="header">City Name: {weatherData.name}</Card.Header>
<p>Temprature: {weatherData.main.temp}</p>
<p>Sunrise: {weatherData.sys.sunrise}</p>
<p>Sunset: {weatherData.sys.sunset}</p>
<p>Description: {weatherData.weather[0].description}</p>
</Card.Content>
</Card>
)
export default CardExampleCard;
気温、日の出、日の入り、説明文をAPIから取得できる。
8. データを整形して、今日の日付けを追加する方法
わかりやすいようにデータをフォーマットするため、いくつかのフィールドを追加。
始めに、温度の単位を追加する。これは温度の後に°Cを付ければOK。
また、日の出と日没を現地時間に変更。
import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react'
const CardExampleCard = ({weatherData}) => (
<Card>
<Card.Content>
<Card.Header className="header">City Name: {weatherData.name}</Card.Header>
<p>Temprature: {weatherData.main.temp} °C</p>
<p>Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}</p>
<p>Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}</p>
<p>Description: {weatherData.weather[0].main}</p>
<p>Humidity: {weatherData.main.humidity} %</p>
</Card.Content>
</Card>
)
export default CardExampleCard;
moment.jsを使って、今日の日付けを追加。
import moment from 'moment';
<p>Day: {moment().format('dddd')}</p>
<p>Date: {moment().format('LL')}</p>
一番上のmomentパッケージを取り込んで、今日の日にちと日付をそれぞれ表示する。このパッケージの優れた点は、日付と曜日を自動的に更新すること。これでweather.jsが完成。
import React from 'react';
import './styles.css';
import { Card } from 'semantic-ui-react';
import moment from 'moment';
const CardExampleCard = ({weatherData}) => (
<Card>
<Card.Content>
<Card.Header className="header">City Name: {weatherData.name}</Card.Header>
<p>Temprature: {weatherData.main.temp} °C</p>
<p>Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}</p>
<p>Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}</p>
<p>Description: {weatherData.weather[0].main}</p>
<p>Humidity: {weatherData.main.humidity} %</p>
<p>Day: {moment().format('dddd')}</p>
<p>Date: {moment().format('LL')}</p>
</Card.Content>
</Card>
)
export default CardExampleCard;
9. スタイリングをしてみよう
データが揃ったところで、より魅力的にするためにスタイリングを行う。
まず、カードのサイズを大きくし、border-radiusを変更し、クールなフォントとカラーを追加し、テキストのアライメントを削除。
import React from 'react';
import './styles.css';
import moment from 'moment';
const CardExampleCard = ({weatherData}) => (
<div className="main">
<p className="header">{weatherData.name}</p>
<div>
<p className="day">Day: {moment().format('dddd')}</p>
</div>
<div>
<p className="temp">Temprature: {weatherData.main.temp} °C</p>
</div>
</div>
)
export default CardExampleCard;
@import url('https://fonts.googleapis.com/css2?family=Recursive&display=swap');
.main{
width: 700px;
border-radius: 15px;
background-color: #01579b;
}
.header{
background-color: #424242;
color: whitesmoke;
padding: 10px;
font-size: 28px;
border-radius: 15px;
font-family: 'Recursive', sans-serif;
}
.day{
padding: 15px;
color: whitesmoke;
font-family: 'Recursive', sans-serif;
font-size: 24px;
font-weight: 600;
}
.temp{
padding: 15px;
color: whitesmoke;
font-family: 'Recursive', sans-serif;
font-size: 18px;
}
フレックスボックスを使って、データを列ごとに並べる。
<div className="flex">
<p className="day">Day: {moment().format('dddd')}</p>
</div>
<div className="flex">
<p className="temp">Temprature: {weatherData.main.temp} °C</p>
</div>
divの名前を「flex」とし、styles.cssに以下のプロパティを追加。
.flex{
display: flex;
justify-content: space-between;
}
weather.jsは以下のようになる。
import React from 'react';
import './styles.css';
import moment from 'moment';
const CardExampleCard = ({weatherData}) => (
<div className="main">
<p className="header">{weatherData.name}</p>
<div className="flex">
<p className="day">Day: {moment().format('dddd')}</p>
<p className="day">{moment().format('LL')}</p>
</div>
<div className="flex">
<p className="temp">Temprature: {weatherData.main.temp} °C</p>
<p className="temp">Humidity: {weatherData.main.humidity} %</p>
</div>
</div>
)
export default CardExampleCard;
10. 同様に、残りのフィールドも追加
import React from 'react';
import './styles.css';
import moment from 'moment';
const WeatherCard = ({weatherData}) => (
<div className="main">
<p className="header">{weatherData.name}</p>
<div className="flex">
<p className="day">{moment().format('dddd')}, <span>{moment().format('LL')}</span></p>
<p className="description">{weatherData.weather[0].main}</p>
</div>
<div className="flex">
<p className="temp">Temprature: {weatherData.main.temp} °C</p>
<p className="temp">Humidity: {weatherData.main.humidity} %</p>
</div>
<div className="flex">
<p className="sunrise-sunset">Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}</p>
<p className="sunrise-sunset">Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}</p>
</div>
</div>
)
export default WeatherCard;
@import url('https://fonts.googleapis.com/css2?family=Recursive&display=swap');
.main{
width: 700px;
border-radius: 20px;
background-color: #01579b;
}
.top{
height: 60px;
background-color: #424242;
color: whitesmoke;
padding: 10px;
border-radius: 20px 20px 0 0;
font-family: 'Recursive', sans-serif;
display: flex;
justify-content: space-between;
}
.header{
background-color: #424242;
color: whitesmoke;
margin: 10px 0px 0px 10px;
font-size: 25px;
border-radius: 20px 20px 0 0;
font-family: 'Recursive', sans-serif;
}
.day{
padding: 15px;
color: whitesmoke;
font-family: 'Recursive', sans-serif;
font-size: 24px;
font-weight: 600;
}
.temp{
padding: 15px;
color: whitesmoke;
font-family: 'Recursive', sans-serif;
font-size: 18px;
}
.flex{
display: flex;
justify-content: space-between;
}
.sunrise-sunset{
padding: 15px;
color: whitesmoke;
font-family: 'Recursive', sans-serif;
font-size: 16px;
}
.description{
padding: 15px;
color: whitesmoke;
font-family: 'Recursive', sans-serif;
font-size: 24px;
font-weight: 600;
}
11. リフレッシュボタンの追加方法
ページの上部にリフレッシュボタンを追加。
import React from 'react';
import './styles.css';
import moment from 'moment';
import { Button } from 'semantic-ui-react';
const refresh = () => {
window.location.reload();
}
const WeatherCard = ({weatherData}) => (
<div className="main">
<div className="top">
<p className="header">{weatherData.name}</p>
<Button className="button" inverted color='blue' circular icon='refresh' onClick={refresh} />
</div>
<div className="flex">
<p className="day">{moment().format('dddd')}, <span>{moment().format('LL')}</span></p>
<p className="description">{weatherData.weather[0].main}</p>
</div>
<div className="flex">
<p className="temp">Temprature: {weatherData.main.temp} °C</p>
<p className="temp">Humidity: {weatherData.main.humidity} %</p>
</div>
<div className="flex">
<p className="sunrise-sunset">Sunrise: {new Date(weatherData.sys.sunrise * 1000).toLocaleTimeString('en-IN')}</p>
<p className="sunrise-sunset">Sunset: {new Date(weatherData.sys.sunset * 1000).toLocaleTimeString('en-IN')}</p>
</div>
</div>
)
export default WeatherCard;
.button{
width: 35px;
height: 35px;
}
リフレッシュ機能のトリガーとなるボタンが表示され、このボタンを押すと、ページが更新される。
12. アプリケーションがロード中にローダーを追加する方法。
Semantic UIからLoaderをインポートして、未定義のデータをチェックするreturn関数の中に追加する。
import { Dimmer, Loader } from 'semantic-ui-react';
<div className="App">
{(typeof data.main != 'undefined') ? (
<Weather weatherData={data}/>
): (
<div>
<Dimmer active>
<Loader>Loading..</Loader>
</Dimmer>
</div>
)}
</div>
13.これまでの作業を振り返る
位置情報に基づいて現在の天気を表示するReactアプリケーションを作成。
これまでに行ったことを振り返る。
① StateとPropsについて学ぶ
StateとPropsはReactの非常に強力な機能で、データを管理したり、異なるコンポーネント内のデータの流れを制御するために使用される。
今回作成したアプリケーションでは、アプリケーションの状態を表すステートを管理している。例えば、都市の名前、温度、日付、湿度などです。それらは、ユーザーごとに、場所によって異なる。
一方、プロップは、コンポーネント間でデータを受け渡すために使われ、app.jsファイルでデータを取得していますが、weather.jsでデータを読み込んでいる。プロップは、親コンポーネントから子コンポーネントへのデータの受け渡しにのみ使用できることを覚えておく。
② React Hooksを使用
クラスコンポーネントを使ったことがあれば、ライフサイクルメソッドについて理解出来ているはず。ライフサイクルメソッドとは、ページのレンダリングや再レンダリングの際に呼び出されるメソッドのこと。しかし、ライフサイクルメソッドはクラスコンポーネント用に特別に作られているため、機能コンポーネントでは使用できない。
そこで、React Hooksがその代わりとなり、このアプリケーションでは2つのフックを使用している。1つはuseStateで、アプリケーションの状態を管理するために使用。もう1つは useEffect で、ページがレンダリングされたりロードされたりしたときにロードされる。
③ Semantic UIを試してみた
Semantic UIはReact用のライブラリで、あらかじめ素晴らしいコンポーネントが定義されている。