์ด์ ๋ openweathermap API๋ก ํ์ฌ ๋ด ์์น์ 5์ผ๊ฐ์ ๋ ์จ๋ฅผ ๋ฐ์์ค๊ณ ,
styled-components๋ฅผ ์ฌ์ฉํด ์คํ์ผ์ ์ ์ฉํ๋ ๊ณผ์ ๋ฅผ ์ํํ๋๋ฐ
์ฐ๋ํ๋๋ฐ ์๊ฐ์ ๋ง์ด ์ผ์ด์ ์ด๋ฒ ๊ธฐํ์ ์ ๋ฆฌํด๋๊ณ ์ ํ๋ค (์์ ์ ๋ฐ๋๋ผ ์๋ฐ์คํฌ๋ฆฝํธ๋ก๋ ๋ฐ์์๋๋ฐ๋... )
์ผ๋จ ์ฌ์ฉํด์ ์์ฑํ ์ฝ๋๋ฅผ App.jsx์ ํ๋ฒ์ ์ ์ด๋จ๋ค
App.jsx
import { useState, useEffect } from 'react';
import { styled, createGlobalStyle } from 'styled-components';
// styled-components ------------------
const Container = styled.div`
width: 100vw;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
`;
const Title = styled.h2`
text-align: center;
margin-bottom: 2em;
`;
const WeatherCardContainer = styled.ul`
display: grid;
grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
justify-items: center;
width: 100%;
list-style: none;
margin: 0 auto;
padding: 20px;
gap: 10px;
`;
const WeatherCard = styled.li`
display: flex;
width: 200px;
height: 150px;
justify-items: start;
border-radius: 10px;
border: 1px solid #eee;
padding: 10px;
flex-direction: column;
justify-content: space-around;
`;
const WeatherCardTitle = styled.div`
width: 100%;
display: flex;
align-items: center;
justify-content: space-around;
img {
width: 30px;
}
`;
const WeatherCardContent = styled.div`
width: 100%;
display: flex;
flex-direction: column;
align-items: center;
`;
const GlobalStyle = createGlobalStyle`
* {
margin : 0;
padding : 0;
box-sizing: border-box;
}
`;
// styled-components ๋ ------------------
function App() {
const [weather, setWeather] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
// ์์น ์ ๋ณด ์์ฒญ
navigator.geolocation.getCurrentPosition(
(position) => {
const latitude = position.coords.latitude;
const longitude = position.coords.longitude;
// const { latitude, longitude } = position.coords;
const API_KEY = import.meta.env.VITE_WEATHER_API_KEY;
// fetch ํธ์ถ (Promise ์ฒด์ธ ์ฌ์ฉ)
fetch(
`https://api.openweathermap.org/data/2.5/forecast?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric&lang=kr`,
)
.then((res) => {
if (!res.ok) {
throw new Error('๋ ์จ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค.');
}
return res.json();
})
.then((data) => {
// 3์๊ฐ ๊ฐ๊ฒฉ ๋ฐ์ดํฐ ์ค์์ ๋งค์ผ 12:00:00 ๋ฐ์ดํฐ๋ง ํํฐ๋ง
const dailyForecast = data.list.filter((item) =>
item.dt_txt.includes('12:00:00'),
);
// ์๋ณธ ๋ฐ์ดํฐ์ list๋ฅผ ๋์ฒดํด์ ์ ์ฅ
setWeather({ ...data, list: dailyForecast });
})
.catch((err) => {
setError(err.message);
});
},
(err) => {
setError(err.message);
},
);
}, []);
if (error) return <p>{error}</p>;
return (
<>
<GlobalStyle />
<Container>
{weather ? (
<>
<Title>{weather.city.name} - 5Days Weather</Title>
<WeatherCardContainer>
{weather.list.map((item) => (
<WeatherCard key={item.dt}>
<WeatherCardTitle>
{item.dt_txt.slice(0, 10)}
<img
alt={item.weather[0].main}
src={`https://openweathermap.org/img/wn/${item.weather[0].icon}.png`}
/>
</WeatherCardTitle>
<WeatherCardContent>
<div>{item.weather[0].description}</div>
<div>{item.main.temp}°C</div>
</WeatherCardContent>
</WeatherCard>
))}
</WeatherCardContainer>
</>
) : (
<p>๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ์ค</p>
)}
</Container>
</>
);
}
export default App;
1. API ํค ๋ฐ๊ธ๋ฐ๊ธฐ
์ผ๋จ openweathermap์ ๊ณต์ ํํ์ด์ง์ ๋ค์ด๊ฐ์ ํ์๊ฐ์ ์ ํด์ผํ๋ค. (์ด๋ฉ์ผ ์ธ์ฆ ๋นผ๊ณ ๋ ๋ฒ๊ฑฐ๋ก์ด๊ฑฐ ์๋ค)
์๋ฌดํผ ํ์๊ฐ์ ์ ํ๊ณ ๋ฉ๋ด์์ ๋ง์ดํ์ด์ง(๋ด ์ด๋ฆ)์ ํด๋ฆญํ๋ค (๋ก๊ทธ์์์ ์ ๊ฐ์ด ํ์ํด๋์ง..)
๋ง์ดํ์ด์ง์ ๋ค์ด๊ฐ์ API keys ํญ์ ๋๋ฅด๋ฉด ๊ธฐ๋ณธ์ผ๋ก ์ฃผ๋ API key๊ฐ ์๋ค.
๊ฐ๋ ค๋ ๋ถ๋ถ์ ๋ด ๊ฐ์ธ ๋ฐ๊ธํค๋ผ์ ๊ฐ๋ ค๋๊ณ , ๊ฐ์ ๋ณธ์ธ์ ํค๋ฅผ ๋ณต์ฌํด๋๋ฉด ๋๋ค.
1) .env ํ์ผ์ ๋ง๋ค์ด ํ๊ฒฝ๋ณ์ ๊ด๋ฆฌํ๊ธฐ
DB ์ฐ๊ฒฐ์ ์ฌ์ฉ์ ๋น๋ฐ๋ฒํธ๋ ํฌํธ๋ฒํธ, ํน์ ์ด๋ฐ API์ key๊ฐ๊ฐ์ ์ค์ํ ์ ๋ณด๋ค์ .envํ์ผ (ํ๊ฒฝ๋ณ์ ํ์ผ)์ ์์ฑํด๋๋ฉด ํ๋์ฝ๋ฉ ์์ด๋ ๋ถ๋ฌ์ฌ ์ ์๋ค.
.env
- ํ๋ก์ ํธ์ ๋ฃจํธ ๋๋ ํ ๋ฆฌ์ ํ์ผ ์์ฑํ๊ธฐ
- ํ์ผ๋ช ์ ๊ทธ๋ฅ .env ๋ก ์ง์ด์ผํจ (setting.env ์ด๋ฐ๊ฑฐ ์ ๋ ์๋จ ๋ฌด์กฐ๊ฑด .env)
- ๋ณ์๋ช , ๊ฐ ์ฌ์ด์ ๊ณต๋ฐฑ์ด๋ ๋ฐ์ดํ ๋ฑ์ด ์กด์ฌํ๋ฉด ์๋จ
- Vite๋ก ์์ฑํ ํ๋ก์ ํธ์์๋ ๋ณ์๋ช ์ ์ ๋์ฌ๊ฐ ๋ฐ๋์ VITE_๋ก ์์๋์ด์ผํจ(CRA๋ REACT_APP_๋ก)
- github์ pushํ ์์ ์ด๋ผ๋ฉด .gitignore ํ์ผ์ .env๋ฅผ ๋ฐ๋์ ์ถ๊ฐํ ๊ฒ. (์ ์ด์ ์จ๊ธฐ๋ ค๊ณ ์์ฑํ๊ฑด๋ฐ .env๊ฐ ๊ฐ์ด ์ฌ๋ผ๊ฐ๋ฉด ๊ทธ๋ฅ ํ์ผ ํ๋ ๋ ๋ง๋ ์ฌ๋์ด ๋๊ธฐ ๋๋ฌธ์ด๋ค...)
// VITE๋ก ์์ฑํ ํ๋ก์ ํธ
VITE_WEATHER_API_KEY=abcdefghijklmnop
// CRA๋ก ์์ฑํ ํ๋ก์ ํธ
REACT_APP_WEATHER_API_KEY=abcdefghijklmnop
๊ทธ๋ ๊ฒ ๋ง๋ ํ๊ฒฝ๋ณ์๋ ๋ค์๊ณผ ๊ฐ์ด ์ ๊ทผํ ์ ์๋ค.
๋๋ vite๋ก ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ธฐ ๋๋ฌธ์ CRA ๋ฒ์ ์ ์ฃผ์์ฒ๋ฆฌ,,
// Vite ํ๋ก์ ํธ์ผ ๋
const API_KEY = import.meta.env.VITE_WEATHER_API_KEY;
// CRA ํ๋ก์ ํธ์ผ ๋
// const API_KEY = process.env.REACT_APP_WEATHER_API_KEY;
2. ์์น ๊ฐ์ ธ์ค๊ธฐ
function App() {
const [weather, setWeather] = useState(null);
const [error, setError] = useState(null);
useEffect(() => {
// ์์น ์ ๋ณด ์์ฒญ
navigator.geolocation.getCurrentPosition(
// position <- GeolocationPosition ํ์
์ ๊ฐ์ฒด
(position) => {
// ๊ตฌ์กฐ ๋ถํด ํ ๋น ๋ฌธ๋ฒ
const { latitude, longitude } = position.coords
// ์๋ต......
},
(err) => {
// ์คํจ ์ ์ ๋ฌ๋ฐ์ ์๋ฌ๊ฐ์ฒด์ ๋ฉ์์ง๋ฅผ setError๋ก ์ค์ ํด์ค
setError(err.message);
},
);
}, []); // ๋ง์ดํธ ์ ํ๋ฒ๋ง ์คํ
Geolocation API
์น ๋ธ๋ผ์ฐ์ ์์ ์ฌ์ฉ์์ ๋์ ํ์ ์์น ์ ๋ณด์ ์ ๊ทผํ ์ ์๋ API๋ก, ์ฌ์ฉ์์ ํ์ฌ ์์น๋ฅผ ๊ฐ์ ธ์ฌ ์ ์๊ฒ ํ๋ค.
๋น๋๊ธฐ์ ์ผ๋ก ๋์ํ๋ฉฐ, HTTPS ํ๊ฒฝ์์๋ง ์๋ํ๋ค.
navigator.geolocation
๋ธ๋ผ์ฐ์ ๊ฐ ์ง์ํ๋ Geolocation ๊ฐ์ฒด๋ฅผ ๋ฐํํ๋ ์์ฑ์ผ๋ก, geolocation์ด ์กด์ฌํ ๊ฒฝ์ฐ ์์น ์ ๋ณด๊ฐ ์ฌ์ฉ์ด ๊ฐ๋ฅํจ
- navigator → ๋ธ๋ผ์ฐ์ ์ ๋ํ ์ ๋ณด๋ฅผ ๋ด์ ์ ์ญ ๊ฐ์ฒด (navigator.userAgent, navigator.language, navigator.onLine ๋ฑ)
- geolocation → ์์น๋ฅผ ๋ค๋ฃจ๋ ์๋ธ ๊ฐ์ฒด
- (์์ธํ ์ ๋ณด๋ MDN ์์)
์ฅ์น์ ํ์ฌ ์์น๋ฅผ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
ํ๋ผ๋ฏธํฐ๋ก๋ (success, error, options)๊ฐ ์๋ค. options๋ ์ ํ๊ฐ์ด๋ผ ํ์์ผ๋ก (์ฌ์ค error๋ ์ ํ๊ฐ์ด์ง๋ง ๋ณดํต ๊ฐ์ด ์ฒ๋ฆฌํ๋๊น..)
- success : GeolocationPosition ๊ฐ์ฒด๋ฅผ ์ ์ผํ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๋ ์ฝ๋ฐฑํจ์
- error : GeolocationPositionError ๊ฐ์ฒด๋ฅผ ์ ์ผํ ๋งค๊ฐ๋ณ์๋ก ๋ฐ๋ ์ฝ๋ฐฑํจ์
position ๋ด์๋ coords ๊ฐ์ฒด๊ฐ ๋ค์ด์๋๋ฐ ์ด ์์ latitude์ longitude ๊ฐ์ด ๋ค์ด์๋ค.
์ด๋ฅผ ๊ตฌ์กฐ ๋ถํด ํ ๋น ๋ฌธ๋ฒ์ ์ฌ์ฉํด ํ์ค๋ก ์ถ์ฝํ ์ ์๋ค.
const latitude = coords.latitude;
const longitude = coords.longitude;
// =
// latitude๋ longitude๋ผ๋ ์์ฑ ์ด๋ฆ์ ์ ํํ ์ผ์น์์ผ์ ๊บผ๋ด์ด (๊ฐ์ ์ด๋ฆ์ ๋ณ์๋ก ๋ณต์ฌํ๋๊ฒ)
const { latitude, longitude } = coords;
3. ๋ ์จ ๋ฐ์ดํฐ ์์ฒญ
์์น๋ฅผ ๋ฐ์์๋ค๋ ์ ์ ํ์ ๋ ์จ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ์ ์์ผ๋ฏ๋ก ๋ ์จ ๋ฐ์ดํฐ๋ ์์น๋ฐ์ดํฐ๋ฅผ ์ฑ๊ณต์ ์ผ๋ก ๋ฐ์์์ ๋ ์ด๋ฃจ์ด์ง๋ค.
(position) => {
const { latitude, longitude } = position.coords;
const API_KEY = import.meta.env.VITE_WEATHER_API_KEY;
// fetch ํธ์ถ
fetch(
`https://api.openweathermap.org/data/2.5/forecast?lat=${latitude}&lon=${longitude}&appid=${API_KEY}&units=metric&lang=kr`,
)
//
.then((res) => {
if (!res.ok) { // res์ ์ํ๊ฐ ok๊ฐ ์๋๋ผ๋ฉด
// ๋ฐ๋ก ์ฒซ๋ฒ์งธ catch ๋ธ๋ก์ผ๋ก ์ ๋ฌ๋จ
throw new Error('๋ ์จ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค์ง ๋ชปํ์ต๋๋ค.');
}
return res.json(); // ์ฑ๊ณต์ json ํ์ฑ ๊ฐ์ฒด ๋ฐํ
})
.then((data) => {
// 3์๊ฐ ๊ฐ๊ฒฉ ๋ฐ์ดํฐ ์ค์์ ๋งค์ผ 12:00:00 ๋ฐ์ดํฐ๋ง ํํฐ๋ง
const dailyForecast = data.list.filter((item) =>
item.dt_txt.includes('12:00:00'),
);
// ์๋ณธ ๋ฐ์ดํฐ์ list๋ฅผ ๋์ฒดํด์ ์ ์ฅ
setWeather({ ...data, list: dailyForecast });
})
.catch((err) => {
setError(err.message);
});
},
API ํธ์ถ ์ ํ์ํ ๊ฐ (๋ ์์ธํ ๋ด์ฉ์ ๊ณต์ ์ฌ์ดํธ ์ฐธ๊ณ )
๋งค๊ฐ๋ณ์ | ์ ๊ณต ํด์ผ ํ ๊ฐ (value) |
lat | ์๋ ์์น |
lon | ๊ฒฝ๋ ์์น |
appid | ๊ณ ์ API ํค |
lang | ๋งค๊ฐ๋ณ์ lang์ ์ฌ์ฉํ์ฌ ํด๋น ์ธ์ด๋ก ์ถ๋ ฅ์ ์ป์ ์ ์์ (๊ธฐ๋ณธ๊ฐ ์์ด) ํ๊ตญ์ด : kr |
unit | ํ์จ ์จ๋ units=imperial ์ญ์จ ์จ๋ units=metric (์ญ์จ ์จ๋ ์ฌ์ฉํ ๊ฑฐ์!) |
๋ฐ์์จ api์ ๊ฐ ๋ฐ์ดํฐ๋ ์๋์ ๊ฐ์ ๊ตฌ์กฐ๋ก ์ด๋ฃจ์ด์ ธ ์๋ค.
์ผ๋จ 5์ผ ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์์ผํ๋๋ฐ ๋ฐ์์ค๋ ์๊ฐ ๊ธฐ์ค์ ์ ์ค (12:00)๋ก ์ก์์ผ๋ฏ๋ก date๋ฅผ ๋ํ๋ด๋ ์์ฑ์ธ dt_txt์์ 12:00:00์ ํฌํจํ๊ณ ์๋ ๊ฐ์ฒด๋ง ํํฐ๋งํ๋ค.
์ด์ ์๋ ์์ฑ์ผ๋ก ๊ฐ์ ๊ฐ์ ธ์ ๋ธ๋ผ์ฐ์ ์ ๊ทธ๋ ค์ค์ผ ํ๋ค.
{
"cod": "200",
"message": 0,
"cnt": 40,
"list": [
{
"dt": 1745334000,
"main": {
"temp": 13.48,
"feels_like": 12.35,
"temp_min": 13.48,
"temp_max": 13.48,
"pressure": 1009,
"sea_level": 1009,
"grnd_level": 992,
"humidity": 56,
"temp_kf": 0
},
"weather": [
{
"id": 801,
"main": "Clouds",
"description": "์ฝ๊ฐ์ ๊ตฌ๋ฆ์ด ๋ ํ๋",
"icon": "02n"
}
],
"clouds": {
"all": 18
},
"wind": {
"speed": 1.11,
"deg": 266,
"gust": 1.34
},
"visibility": 10000,
"pop": 0,
"sys": {
"pod": "n"
},
"dt_txt": "2025-04-22 15:00:00"
}
],
"city": {
"id": '์ฐ๋ฆฌ ๋๋ค๋ผ์ ์๋ต..',
"name": "์ฐ๋ฆฌ ๋๋ค๋ผ์ ์๋ต..",
"coord": {
"lat": "์ฐ๋ฆฌ ๋๋ค๋ผ์ ์๋ต..",
"lon": "์ฐ๋ฆฌ ๋๋ค๋ผ์ ์๋ต.."
},
"country": "KR",
// ์๋ต
}
}
4. ๋ธ๋ผ์ฐ์ ์ ํ์
์์ด์ฝ์ ํ์ํ๋ ๋ฐฉ๋ฒ์ ๊ณต์์ฌ์ดํธ์ ํด๋น ๋ถ๋ถ์ ์ฐธ๊ณ
weather์ ๊ฐ์ด ์์ ๊ฒฝ์ฐ (์ ๋ฐ์์์ ๊ฒฝ์ฐ) ๊ฐ์ ๋ฟ๋ ค์ฃผ๊ณ ์๊ณ ๊ทธ๊ฒ ์๋ ๊ฒฝ์ฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ์ค์ ๋ฉ์์ง๋ฅผ ํ์ํด์ฃผ๊ณ ์๋ค.
// ๋ง์ฝ error๊ฐ null์ด ์๋๋ผ๋ฉด ์๋ฌ ๋ฉ์์ง๋ง ํ๋ฉด์ ๋ ๋๋ง
if (error) return <p>{error}</p>;
return (
<>
<GlobalStyle />
<Container>
{weather ? (
<>
<Title>{weather.city.name} - 5Days Weather</Title>
<WeatherCardContainer>
{weather.list.map((item) => (
<WeatherCard key={item.dt}>
<WeatherCardTitle>
{item.dt_txt.slice(0, 10)}
<img
alt={item.weather[0].main}
src={`https://openweathermap.org/img/wn/${item.weather[0].icon}.png`}
/>
</WeatherCardTitle>
<WeatherCardContent>
<div>{item.weather[0].description}</div>
<div>{item.main.temp}°C</div>
</WeatherCardContent>
</WeatherCard>
))}
</WeatherCardContainer>
</>
) : (
<p>๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ค๋ ์ค</p>
)}
</Container>
</>
);
}
๊ฒฐ๊ณผ ํ๋ฉด
์ฐธ๊ณ
.env ํ์ผ ์์ฑํ๊ธฐ : http://rominlamp.tistory.com/22
https://velog.io/@hoho_0815/env-%ED%8C%8C%EC%9D%BC%EC%97%90-%EB%8C%80%ED%95%98%EC%97%AC
https://ko.vite.dev/guide/env-and-mode
์์น ๋ฐ์์ค๊ธฐ : https://inpa.tistory.com/entry/JS-%F0%9F%93%9A-Geolocation-API%EB%A1%9C-%F0%9F%97%BA%EF%B8%8F-%EC%9C%84%EB%8F%84-%EA%B2%BD%EB%8F%84-%EC%96%BB%EA%B3%A0-%E2%9B%85-%EB%82%A0%EC%94%A8-%EC%98%A8%EB%8F%84-%EC%A0%95%EB%B3%B4%EB%A5%BC-%EC%96%BB%EC%96%B4%EC%98%A4%EA%B8%B0
openweathermap API ์ฌ์ฉํ๊ธฐ :
https://jisilver-k.tistory.com/71
'react' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
React / ์ ์ญ ์ํ ๊ด๋ฆฌ (2) - Redux, Redux Toolkit (0) | 2025.04.22 |
---|---|
React / ์ ์ญ ์ํ ๊ด๋ฆฌ (1) - ๋ด๋ถ (0) | 2025.04.18 |
React / Todo-List ๋ง๋ค๊ธฐ (2) (0) | 2025.04.17 |
React / ์คํ์ผ๋ง (3) - tailwindcss (0) | 2025.04.17 |
React / ์คํ์ผ๋ง (2) - Styled Components (1) | 2025.04.16 |