💡 무엇을 배울까
1. React 개발을 위한 VS Code 설정
2. 파일 구조
3. 프로젝트 전체 동작 흐름
4. OpenWeatherMap API 가져오는 방법
5. WeatherPage에서 화면에 날씨 정보 표시하기
📖 수업 내용
1️⃣ React 개발을 위한 VS Code 설정
프로젝트를 시작하기 앞서 먼저 VS Code를 설정하자.
React 개발을 위한 VS Code 설정
⚙ VS Code 설정1️⃣ VS Code 설치 Visual Studio Code - Code Editing. RedefinedVisual Studio Code redefines AI-powered coding with GitHub Copilot for building and debugging modern web and cloud applications. Visual Studio Code is free and available o
jugang.tistory.com
필요한 패키지까지 설치하면 (아래 코드 참고)
# 라우팅
npm install react-router-dom
# API 호출
npm install axios
npm install @tanstack/react-query
npm install @tanstack/react-query-devtools
초기 설정 완료!!
2️⃣ 파일 구조
/src
├── assets
│ ├── index.css ------------------> # 전역 CSS 스타일 파일
│ └── react.svg
│
├── layout -------------------------> # 공통 레이아웃 컴포넌트 모음
│ ├── MainLayout.jsx -------------> # 전체 페이지 공통 레이아웃 컴포넌트
│ ├── MainLayout.module.css ------> # MainLayout 전용 모듈 CSS
│ ├── MenuList.jsx ---------------> # 메뉴 리스트 컴포넌트
│ └── MenuList.module.css --------> # MenuList 전용 모듈 CSS
│
├── router -------------------------> # 라우터 설정 폴더
│ └── index.jsx ------------------> # React Router 설정 파일
│
├── weather ------------------------> # 날씨 관련 기능 담당 폴더
│ ├── Button.jsx -----------------> # 버튼 컴포넌트
│ ├── Button.module.css ----------> # Button 전용 모듈 CSS
│ ├── useWeatherApi.js -----------> # 날씨 API 호출용 커스텀 훅 (API 통신)
│ ├── WeatherPage.jsx ------------> # 날씨 페이지 컴포넌트
│ └── WeatherPage.module.css -----> # WeatherPage 전용 모듈 CSS
│
└── main.jsx -----------------------> # React 앱 진입점 (루트 파일)
/.env.local --------------------------> # 환경변수 파일 (로컬 전용)
3️⃣ 프로젝트 전체 동작 흐름
1. 진입: `main.jsx`
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import '@/assets/index.css'
import { RouterProvider } from 'react-router-dom'
import { router } from './router'
createRoot(document.getElementById('root')).render(
<StrictMode>
<RouterProvider router={router} />
</StrictMode>
)
- React 앱을 root에 마운트한다.
- `RouterProvider`를 통해 라우팅 시스템을 설정한다.
2. 라우팅 설정: router > `index.jsx`
import { createBrowserRouter } from 'react-router-dom'
import MainLayout from '../layout/MainLayout'
import WeatherPage from '../weather/WeatherPage'
export const router = createBrowserRouter([
{
path: '/',
element: <MainLayout />,
errorElement: <div>에러</div>,
children: [
{
index: true,
element: <WeatherPage />,
},
],
},
])
- `/` 경로에 `MainLayout.jsx`를 보여준다.
- `MainLayout` 내부에서 `WeatherPage.jsx`를 렌더링한다.
3. 공통 레이아웃: layout > `MainLayout.jsx`
import React from 'react'
import MenuList from './MenuList'
import { Outlet } from 'react-router-dom'
import css from './MainLayout.module.css'
const MainLayout = () => {
return (
<div className={css.layout}>
<MenuList />
<Outlet />
</div>
)
}
export default MainLayout
- 좌측 메뉴와 메인 콘텐츠 영역을 분리해서 구성
- `<MenuList />`: 사이드 메뉴 (네비게이션)
- `<Outlet />`: 현재 경로에 맞는 하위 컴포넌트 (WeatherPage) 렌더링
4. 메뉴 리스트: layout > `MenuList.jsx`
import React from 'react'
import { NavLink } from 'react-router-dom'
import css from './MenuList.module.css'
const MenuList = () => {
return (
<>
<ul>
<li>
<NavLink to={'/'} className={isActive => (isActive ? `${css.active}` : '')}>
날씨 API 활용
</NavLink>
</li>
</ul>
</>
)
}
export default MenuList
- 사이드 메뉴에 `NavLink`를 사용하여 `/` 경로로 이동하는 메뉴 제공
- 현재는 "날씨 API" 한 개만 등록
5. 날씨 페이지: weather > `WeatherPage.jsx`
import React, { useEffect, useState } from 'react'
import css from './WeatherPage.module.css'
import { getCountryData, getCurrentData } from './useWeatherApi'
import Button from './Button'
import { useSearchParams } from 'react-router-dom'
const WeatherPage = () => {
const [searchParams, setSearchParams] = useSearchParams()
const city = searchParams.get('city')
const [weatherData, setWeatherData] = useState(null)
const cityButtons = [
{ id: 'current', label: '현재 위치' },
{ id: 'seoul', label: '서울' },
{ id: 'hongkong', label: '홍콩' },
{ id: 'new york', label: '뉴욕' },
{ id: 'paris', label: '파리' },
]
useEffect(() => {
const fetchWeatherData = async () => {
try {
let data
if (city) {
data = await getCountryData(city)
} else {
data = await getCurrentData()
}
setWeatherData(data)
} catch (err) {
console.log('날씨 데이터 가져오기 실패----', err)
}
}
fetchWeatherData()
}, [city])
const handleChangeCity = city => {
if (city === 'current') {
setSearchParams({})
} else {
setSearchParams({ city })
}
}
return (
<main className={css.main}>
<section className={css.weatherCard}>
<h2 className={css.title}>날씨</h2>
<p className={css.location}>
{weatherData?.sys.country} / {weatherData?.name}
</p>
<div className={css.temperature}>
<p>{weatherData?.main.temp.toFixed(1)}℃</p>
<img
src={`https://openweathermap.org/img/wn/${weatherData?.weather[0].icon}@2x.png`}
alt="weather-icon"
className={css.weatherIcon}
/>
</div>
</section>
<div className={css.btnList}>
{cityButtons.map(button => (
<Button
key={button.id}
city={button.id}
label={button.label}
onClick={handleChangeCity}
/>
))}
</div>
</main>
)
}
export default WeatherPage
- 현재 위치 또는 도시 이름으로 날씨 데이터를 가져온다.
- 버튼을 클릭하면 `useSearchParams`로 URL을 바꾸고, 그에 따라 다시 날씨 데이터를 불러온다.
📌 핵심 동작:
- `useEffect`로 URL 변경 감지
- `getCurrentData` (현재 위치) 또는 `getCountryData` (도시명)로 API 요청
- 받아온 데이터 화면에 표시
6. 버튼: weather > `Button.jsx`
import React from 'react'
import css from './Button.module.css'
const Button = ({ city, label, onClick }) => {
return (
<button className={css.button} onClick={() => onClick(city)}>
{label}
</button>
)
}
export default Button
7. API 함수: weather > `useWeatherApi.js`
import axios from 'axios'
const API_KEY = import.meta.env.VITE_WEATHER_API_KEY
const BASE_URL = 'https://api.openweathermap.org/data/2.5/weather'
// 좌표로 날씨 정보 가져오기
export const getWeatherByCurrentLocation = async (lat, lon) => {
try {
const res = await axios.get(
`${BASE_URL}?lat=${lat}&lon=${lon}&appid=${API_KEY}&lang=kr&units=metric`
)
return res.data
} catch (err) {
console.log('좌표로 날씨 정보 가져오기 실패----', err)
}
}
// 현재 위치 날씨 정보 가져오기
// 1. 현재 좌표 가져오기
// 2. getWeatherByCurrentLocation(위도, 경도)
export const getCurrentData = async () => {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
async position => {
try {
const { latitude, longitude } = position.coords
const res = await getWeatherByCurrentLocation(latitude, longitude)
resolve(res)
} catch (err) {
console.log('좌표로 날씨 정보 가져오기 실패', err)
reject(err)
}
},
err => {
console.log('좌표 가져오기 실패', err)
reject(err)
}
)
})
}
// 도시명으로 날씨 정보 가져오기
export const getCountryData = async city => {
try {
const res = await axios.get(`${BASE_URL}?q=${city}&appid=${API_KEY}&lang=kr&units=metric`)
return res.data
} catch (err) {
console.log('좌표로 날씨 정보 가져오기 실패----', err)
}
}
- 날씨 데이터 가져오는 API 통신 모듈
- `getCurrentData()` : 현재 위치 기준 날씨
- `getCountryData(city)` : 도시명 기준 날씨
📊 전체 연결 흐름
index.html
↓
main.jsx (React 앱 시작)
↓
RouterProvider
↓
index.jsx (라우팅 설정)
↓
MainLayout.jsx (레이아웃)
├─ MenuList.jsx (사이드 메뉴)
└─ Outlet → WeatherPage.jsx (메인 콘텐츠)
├─ useWeatherApi.js로 API 호출
└─ Button.jsx 클릭 → 도시 변경
🛠 OpenWeatherMap API 가져오는 방법
1. OpenWeatherMap API 가입 및 API Key 발급
-> 먼저 OpenWeatherMap 사이트에 접속한다.
Current weather and forecast - OpenWeatherMap
Access current weather data for any location on Earth including over 200,000 cities! The data is frequently updated based on the global and local weather models, satellites, radars and a vast network of weather stations. how to obtain APIs (subscriptions w
openweathermap.org
-> 그 다음 회원가입 후 이메일 인증을 한 뒤 로그인한다.
-> 그럼 내 계정에 My API keys 메뉴에서 새 API 키를 발급받을 수 있다.

-> 발급받은 API 키를 `.env.local` 파일에 저장하자.
VITE_WEATHER_API_KEY=발급받은_API_KEY
※ 주의 :
- `.env.local` 파일은 GitHub에 업로드하지 말고 로컬에서만 사용해야 한다.
- `VITE_`로 시작해야 Vite 환경에서 환경변수를 읽을 수 있다.
🤔 `.env` 파일이 궁금하다면 (더보기 참고)
`.env` 파일은
프로젝트 안에서 사용하는 환경 변수(Environment Variables)를 저장해놓는 파일
- 비밀번호, API 키, 서버 주소처럼 외부에 노출되면 안되는 값을 관리할 때 사용
- GitHub 같은 공개 저장소에는 절대 올리지 않고 로컬(내 컴퓨터)에만 저장
- 코드에 민감한 정보를 하드코딩(직접 적는 것)하지 않고,
별도의 파일로 관리해서 보안적으로 안전
Vite, CRA(Create React App), Next.js 같은 현대 프론트엔드 프레임워크들은
환경에 따라 다른 `.env` 파일을 읽을 수 있도록 규칙을 정해놨다.
| 파일명 | 사용 용도 |
| `.env` | 모든 환경(개발, 운영)에 공통으로 적용할 기본 설정 |
| `.env.local` | 내 컴퓨터에서만 적용되는 로컬 전용 설정 (공유 X) |
| `.env.development` | 개발 환경에서만 적용할 설정 |
| `.env.production` | 배포(운영) 환경에서만 적용할 설정 |
| `.env.test` | 테스트 환경에서만 적용할 설정 |
❗ 주의할 점
`.env.local`은 무조건 `.gitignore`에 추가해야 한다.
2. 날씨 API 요청
API 요청을 쉽게 하기 위해 Axios를 사용한다. (이미 처음에 설치했기 때문에 설명은 생략)
📌 weather > `useWeatherApi.js`
# 기본 설정
import axios from 'axios'
const API_KEY = import.meta.env.VITE_WEATHER_API_KEY
const BASE_URL = 'https://api.openweathermap.org/data/2.5/weather'
`API_KEY`는 `.env.local`에서 가져오고,
`BASE_URL`은 OpenWeatherMap의 날씨 데이터 API 기본 URL이다.
# 현재 위치로 날씨 데이터 가져오기
export const getCurrentData = async () => {
return new Promise((resolve, reject) => {
navigator.geolocation.getCurrentPosition(
async position => {
try {
const { latitude, longitude } = position.coords
const res = await getWeatherByCurrentLocation(latitude, longitude)
resolve(res)
} catch (err) {
reject(err)
}
},
err => {
reject(err)
}
)
})
}
- 브라우저의 `navigator.geolocation` API를 사용해 현재 위치(위도, 경도)를 얻는다.
- 얻은 좌푤르 가지고 `getWeatherByCurrentLocation` 함수를 호출해서 날씨 정보를 가져온다.
# 위도와 경도로 날씨 데이터 가져오기
export const getWeatherByCurrentLocation = async (lat, lon) => {
try {
const res = await axios.get(
`${BASE_URL}?lat=${lat}&lon=${lon}&appid=${API_KEY}&lang=kr&units=metric`
)
return res.data
} catch (err) {
console.log('좌표로 날씨 정보 가져오기 실패', err)
}
}
- 위도(`lat`)와 경도(`lon`)를 이용해서 OpenWeatherMap API에 요청한다.
- 한국어(`lang=kr`)로 데이터를 요청하고, 온도는 섭씨(`units=metric`)로 가져온다.
# 도시명으로 날씨 데이터 가져오기
export const getCountryData = async city => {
try {
const res = await axios.get(
`${BASE_URL}?q=${city}&appid=${API_KEY}&lang=kr&units=metric`
)
return res.data
} catch (err) {
console.log('도시명으로 날씨 정보 가져오기 실패', err)
}
}
- 도시 이름(`city`)을 검색어로 날씨 데이터를 요청한다.
🖥 WeatherPage에서 화면에 날씨 정보 표시하기
📌 화면 동작 흐름
- URL에 `city` 값이 있으면 -> 해당 도시 날씨 요청 (`getCountryData`)
- URL에 `city` 값이 없으면 -> 현재 위치 날씨 요청 (`getCurrentData`)
- 날씨 데이터가 받아와지면 -> 화면에 다음 정보 표시
- 나라/도시명 (`sys.country / name`)
- 현재 온도 (`main.temp`)
- 날씨 아이콘 (`weather[0].icon`)
🧾 주요 코드 설명
useEffect(() => {
const fetchWeatherData = async () => {
try {
let data
if (city) {
data = await getCountryData(city)
} else {
data = await getCurrentData()
}
setWeatherData(data)
} catch (err) {
console.log('날씨 데이터 가져오기 실패----', err)
}
}
fetchWeatherData()
}, [city])
-> useEffect로 날씨 데이터 가져오기
여기서 날씨 API로부터 받아온 `data`를 콘솔에 출력해보면 다음과 같은 구조를 볼 수 있다.
console.log('날씨 데이터: ', data)

| 주요 필드 | 설명 |
| coord | 위치 정보 (위도, 경도) |
| weather | 날씨 상태 (맑음, 흐림 등) |
| base | 내부 데이터베이스 종류 |
| main | 온도, 습도, 기압 등 주요 날씨 데이터 |
| name | 도시 이름 |
| sys | 국가 코드, 일출/일몰 시간 등 |
| wind | 바람 속도, 바람 방향 |
| visibility | 가시거리 (미터 단위) |
여기서
`.sys.country`(국가 코드), `.name`(도시명), `.main.temp`(현재 온도), `.weather[0].icon`(날씨 아이콘 코드)
를 사용한다.
<main className={css.main}>
<section className={css.weatherCard}>
<h2 className={css.title}>날씨</h2>
<p className={css.location}>
{weatherData?.sys.country} / {weatherData?.name}
</p>
<div className={css.temperature}>
<p>{weatherData?.main.temp}℃</p>
<img
src={`https://openweathermap.org/img/wn/${weatherData?.weather[0].icon}@2x.png`}
alt="weather-icon"
className={css.weatherIcon}
/>
</div>
</section>
</main>
-> 받아온 데이터를 화면에 표시한다.
🖐🏻 여기서 잠깐
🤔 왜 `?`를 사용해야 할까? (Optional Chaining)
`weatherData?.sys.country` 처럼 `?`를 사용하는 이유는
아직 `weatherData`가 완전히 로딩되지 않았을 때를 대비해서
에러 없이 안전하게 접근하기 위해
💡 상황을 쉽게 이해해보자.
처음 화면이 렌더링될 때를 생각해보면 :
const [weatherData, setWeatherData] = useState(null)
- 처음에는 weatherData가 `null`임 (데이터 없음)
- 그런데 코드에서 만약 그냥 `weatherData.sys.country`처럼 바로 접근하면?
❗ 에러 발생
TypeError: Cannot read properties of null (reading 'sys')
-> 해석 : "null"이라는 값에 대해 'sys' 속성을 읽으려고 해서 에러가 발생했다"
이렇게 접근하면,
- null은 객체가 아니야
- null에는 `sys`라는 속성이 없어
- 그래서 JavaScript가 죽어버리는 거야 ("Cannot read properties of null")
그래서 해결 방법은 바로
Optional Chaining (`?`)을 쓰는 것!
{weatherData?.sys.country}
- weatherData가 null이면 아예 평가를 멈추고(undefined로 처리) 넘어간다.
- weatherData가 있으면 정상적으로 `.sys.country` 읽어온다.
결론적으로,
항상 데이터가 확실히 존재하는지 모를 때는 `?.`로 안전하게 접근하는 습관을 들이자!
<div className={css.btnList}>
{cityButtons.map(button => (
<Button
key={button.id}
city={button.id}
label={button.label}
onClick={handleChangeCity}
/>
))}
</div>
-> 버튼으로 도시 선택하기
- `cityButtons` 배열을 map으로 돌면서 Button 컴포넌트를 여러 개 만든다.
- 버튼을 클릭하면 `handleChangeCity`가 호출되어 URL `city` 파라미터를 변경한다.
- 이 변경을 감지한 `useEffect`가 다시 API를 호출해서 데이터를 업데이트한다.
const Button = ({ city, label, onClick }) => {
return (
<button className={css.button} onClick={() => onClick(city)}>
{label}
</button>
)
}
-> Button 컴포넌트
- `label`로 버튼 텍스트를 표시
- 클릭하면 `onClick` 함수를 호출해서 선택한 도시 정보를 전달한다.

✨ 완성~!
🔍 회고
실습 시작 전에 강사님께서 전 시간에 과제로 내준 용돈기입장 프젝 중
잘했던 사람들을 소개해주셨다.
보면서 난 정말 주어진 대로만 하고 창의적인 생각은 안한다고 느꼈다.
어릴 때부터 뭔갈 창작하고 아이디어 도출하는게 어려웠다.
다양한 방법으로 주어진 예시와 다르게 표현하거나 여러 노력의 흔적을 봤을 때
많은 깨달음이 있었다.
한참 부족한 지식이지만 꾸준히 노력하자...꾸준히.
'URECA - TIL > React 라이브러리' 카테고리의 다른 글
| [React] DAY 12 - 쇼핑몰 프로젝트 (4) (0) | 2025.04.21 |
|---|---|
| [React] DAY 11 - 쇼핑몰 프로젝트 (3) (0) | 2025.04.19 |
| [React] DAY 10 - 쇼핑몰 프로젝트 (2) (2) | 2025.04.15 |
| [React] DAY 9 - 쇼핑몰 프로젝트 (1) (0) | 2025.04.15 |
| [React] DAY 8 - React Router (0) | 2025.04.11 |