날씨 정보 api 가져오기
https://openweathermap.org/api
현재 위치 가져오기
https://www.w3schools.com/html/html5_geolocation.asp
비동기 방식
잡업(task)들이 순차적으로 이루어지지 않는다. 다른 작업들은 non-blocking.
callback
콜백 함수는 인자로 들어오는 함수를 칭한다.
문제점 : 콜백 헬로 인한 에러처리 불가와 가독성 X
promise
비동기 통신의 결과와 상태를 저장하는 객체이다.
후속처리 메서드로 then,catch,finally가 있다.
async/await
promise의 복잡성으로 인해 나온 비동기 패턴 더 사용하기 편하다.
App.js
import './App.css';
import WeatherBox from './components/WeatherBox';
import WeatherButton from './components/WeatherButton';
import {Container} from 'react-bootstrap';
import React, {useState, useEffect} from 'react';
import {ClipLoader} from 'react-spinners';
import 'bootstrap/dist/css/bootstrap.min.css';
const API_KEY = '5c0e4665db050c6343ab348328c9270c'
const cities = ['paris','new york', 'tokyo', 'seoul'];
const App = () => {
const [loading, setLoading] = useState(false);
const [city, setCity] = useState(null);
const [weather, setWeather] = useState(null);
const [apiError, setAPIError] = useState('');
const getWeatherByCurrentLocation = async (lat, lon) => {
console.log('현재 위치', lat, lon);
//비동기 처리
try {
let url = `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}`;
//let url = 'https://api.openweathermap.org/data/2.5/weather?lat=35.8779863&lon=128.5795303&appid=5c0e4665db050c6343ab348328c9270c&units=metric';
const res = await fetch(url);
const data = await res.json();
setWeather(data);
setLoading(false);
}
catch (err) {
console.log(err.message);
setLoading(false);
}
};
function getCurrentLocation() {
navigator.geolocation.getCurrentPosition((position) => {
const { latitude, longitude } = position.coords;
getWeatherByCurrentLocation(latitude, longitude);
// console.log('현재위치?', latitude, longitude);
});
};
const getWeatherByCity = async () => {
try {
let url = `https://api.openweathermap.org/data/2.5/weather?q=${city}&appid=${API_KEY}`;
const res = await fetch(url);
const data = await res.json();
setWeather(data);
setLoading(false);
}catch(err){
console.log(err);
console.log(err);
setLoading(false);
}
};
useEffect(()=>{
if(city == null){
setLoading(true);
getCurrentLocation();
}else{
setLoading(true);
getWeatherByCity();
}
},[city]);
const handleCityChange = (city) => {
if(city === 'current'){
setCity(null);
}else{
setCity(city);
}
};
return (
<Container className="vh-100">
{loading ? (
<div className="w-100 vh-100 d-flex justify-content-center align-items-center">
<ClipLoader color="#f86c6b" size={150} loading={loading} />
</div>
) : !apiError ? (
<div class="main-container">
<WeatherBox weather={weather} />
<WeatherButton
cities={cities}
handleCityChange={handleCityChange}
selectedCity={city}
/>
</div>
) : (
apiError
)}
</Container>
);
}
export default App;
App.css
body{
background: url('https://images.pexels.com/photos/1631677/pexels-photo-1631677.jpeg?cs=srgb&dl=pexels-abdullah-ghatasheh-1631677.jpg&fm=jpg') no-repeat;
height: 100vh;
background-size: cover;
}
.weather-card{
background-color: rgba(255,255,255,.9)!important;
padding: 50px;
border: 2px solid #fff;
border-radius: 20px;
max-width: 700px;
width:100%;
height: 300px;
}
.main-container{
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
}
.menu-container{
display: flex;
justify-content: center;
background-color: rgba(52,52,52,.6);
border-radius: 60px;
max-width: 700px;
width:100%;
padding: 30px;
margin-top: 30px;
}
.menu-container Button{
margin-right:30px;
}
.menu-container Button:hover{
background-color: gray;
}
components/PublicNavar.js
import React from 'react';
import { Navbar, Nav } from 'react-bootstrap';
const PublicNavbar = () => {
return (
<Navbar bg="light" expand="lg" className="position-fixed navbar-fixed">
<Navbar.Brand></Navbar.Brand>
<Nav className="mr-auto"></Nav>
<Nav>
<a
href="https://github.com/dhminh1024/cs_weather_app"
target="_blank"
rel="noreferrer"
></a>
</Nav>
</Navbar>
);
};
export default PublicNavbar;
components/WeatherBox.js
import React from 'react';
import { Card } from 'react-bootstrap';
const WeatherBox = ({ weather }) => {
const temperatureC =
weather && weather.main ? (weather.main.temp - 273.15).toFixed(2) : '';
const temperatureF =
weather && weather.main
? (((weather.main.temp - 273.15) * 9) / 5 + 32).toFixed(2)
: '';
return (
<Card className="weather-card">
{/* <Card.Img src="holder.js/100px270" alt="Card image" /> */}
<Card.ImgOverlay className="d-flex flex-column justify-content-center text-center">
<Card.Title>{weather?.name}</Card.Title>
<Card.Text className="text-success h1">
{`${temperatureC} °C / ${temperatureF} °F`}
</Card.Text>
<Card.Text className="text-info text-uppercase h2">
{weather && weather.weather[0]?.description}
</Card.Text>
</Card.ImgOverlay>
</Card>
);
};
export default WeatherBox;
components/WeatherButton.js
import React from 'react';
import { Button } from 'react-bootstrap';
const WeatherButton = ({ cities, selectedCity, handleCityChange }) => {
return (
<div class="menu-container">
<Button
variant={`${selectedCity === null ? 'outline-secondary' : 'secondary'}`}
onClick={() => handleCityChange('current')}
>
Current Location
</Button>
{cities.map((city) => (
<Button
variant={`${selectedCity === city ? 'outline-secondary' : 'secondary'}`}
onClick={() => handleCityChange(city)}
>
{city}
</Button>
))}
</div>
);
};
export default WeatherButton;
실습 결과
X
X
api와 여러가지 기능들을 만드는 것이 아니라 받아와서 작성하는 것 만으로도 쉽게 작동되니 역시 검색하는 능력과 어떤 좋은 것이 있는 지 폭 넓은 지식이 필요하다는 것을 느꼈습니다.