2026. 7. 10. 16:07ㆍAI 기반 프론트엔드 웹개발자 양성과정

[React] 리스트 렌더링부터 useState · useEffect · fetch까지
2026.07.10 · React + Vite · 컴포넌트 24~39 (Alert ~ Weather)
📋 오늘 한 일
오늘은 객체로 설정값을 묶는 패턴, map으로 배열을 화면 목록으로 그리기, MUI 라이브러리, 여러 컴포넌트 조립을 지나 드디어 React의 진짜 핵심인 useState(상태), useEffect(생명주기), 그리고 fetch로 외부 데이터 가져오기까지 왔다. "데이터가 바뀌면 화면이 자동으로 다시 그려진다"는 걸 오늘 직접 눈으로 확인했다. 중간에 헷갈렸던 구조 분해 할당과 스프레드 연산자는 정리해 코드 주석으로 남겨뒀다.
1. Alert — 객체로 설정값 묶기 (24)
type에 따라 아이콘·색을 다르게 보여주는 알림 컴포넌트. 조건문을 여러 개 쓰는 대신, 객체(map)에 설정값을 미리 묶어두고 type을 키로 꺼내 쓰는 방식을 배웠다. 이렇게 하면 종류가 늘어나도 객체에 한 줄만 추가하면 된다.
// components/24/Alert.jsx const Alert = ({type, text}) => { // 중첩 객체 구조 const map = { success : {icon: '✅', color: 'green'}, error : {icon: '❌', color: 'red'}, warning : {icon: '⚠️', color: 'orange'} } const cfg = map[type] return ( <p style={{color:cfg.color}}>{cfg.icon} {text}</p> ) } export default Alert
// 사용 — 같은 컴포넌트로 3가지 알림 <Alert type="success" text="성공했습니다" /> <Alert type="error" text="실패했습니다" /> <Alert type="warning" text="주의하세요" />
map[type]처럼 변수를 키로 객체에 접근하면, if/else 없이도 "종류 → 설정" 을 한 번에 고를 수 있다. 값이 {icon, color}처럼 객체 안에 또 객체가 들어간 중첩 객체라는 점도 오늘 처음 써봤다.💬 구조 분해 할당 (Destructuring)
({type, text}) => 이 문법이 뭔지 헷갈려서 Claude CLI에 물어봤다. 객체나 배열에서 값을 꺼내 변수에 바로 담는 문법이라고 한다.
// 기존 방식 const name = user.name; const age = user.age; // 구조 분해 (간결) const { name, age, job } = user; // 키 이름과 변수명이 같아야 함
// ② 이름 바꾸기(별칭) — 키이름: 새변수명 const { name: userName, age: userAge } = user; // ③ 기본값 — 없으면 기본값 사용 const { name, age = 0 } = user; // ④ 배열 구조 분해 — 순서로 꺼냄 const [first, second, third] = colors; const [first, , third] = colors; // 가운데 건너뛰기 // ⑤ 중첩 객체 const { name, address: { city, zip } } = user;
| 종류 | 문법 | 기준 |
|---|---|---|
| 객체 | const { a, b } = obj | 키 이름 |
| 배열 | const [a, b] = arr | 순서 |
| props | ({ a, b }) => {} | 키 이름 |
({type, text})는 props.type·props.text를 매개변수 자리에서 바로 분해한 것 — 코드가 훨씬 짧고 읽기 쉬워진다.2. Rating — map으로 별점 그리기 (25)
score만큼 ⭐, 나머지는 ☆로 별 5개를 그리는 컴포넌트. 빈 배열 5칸을 만들어 map으로 돌리는 패턴을 배웠다. 반복되는 화면 요소는 이렇게 배열 + map으로 찍어낸다.
// components/25/Rating.jsx const Rating = ({score}) => { return ( <div> {[...Array(5)].map((_, i) => ( <span key={i}>{i < score ? '⭐' : '☆'}</span> ))} </div> ) } export default Rating // 사용: <Rating score={3}/>
[...Array(5)]는 "빈 칸 5개짜리 배열"을 만드는 관용구. map의 두 번째 인자 i(인덱스)를 score와 비교해 채운 별/빈 별을 정한다. 목록을 map으로 그릴 땐 각 요소에 key를 꼭 붙여야 한다.💬 스프레드 연산자 (Spread)
[...Array(5)]의 ...이 뭔지 몰라서 또 물어봤다. 배열이나 객체를 펼쳐서 개별 요소로 만드는 문법이라고 한다.
// 기본 — 펼치기 const arr = [1, 2, 3]; console.log(arr); // [1, 2, 3] ← 배열 그대로 console.log(...arr); // 1 2 3 ← 펼쳐짐 // 배열 합치기 / 복사 / 중간 추가 const merged = [...a, ...b]; // [1,2,3,4,5,6] const copy = [...original]; // 원본 안 바뀜 const newArr = [0, ...arr, 4, 5];
// 객체 합치기 / 일부만 덮어쓰기 const merged = { ...user, ...job }; const updated = { ...user, job: '개발자' }; // 뒤 값이 앞을 덮음 // 함수 인자 / props 전달 sum(...numbers); // sum(1,2,3) 과 동일 <Alert {...props} /> // type·text 한 번에 전달
Spread ... | Rest ... | |
|---|---|---|
| 위치 | 배열/객체 앞 | 함수 매개변수 |
| 역할 | 펼치기 | 모으기 |
| 방향 | 하나 → 여러 개 | 여러 개 → 하나 |
...은 "이 안에 있는 걸 다 꺼내서 펼쳐라". 반대로 함수 매개변수에서 function fn(...args)로 쓰면 인자들을 배열로 모으는 Rest가 된다.3. Tag — 배열 props를 map + key로 (26)
이번엔 배열 자체를 props로 받아 태그 목록으로 그렸다. 부모가 ['HTML','CSS','JS','REACT']를 넘기면 각 항목 앞에 #을 붙여 뿌린다.
// components/26/Tag.jsx const Tag = ({tags}) => { return ( <div> {tags.map((tag) => ( <span key={tag}>{'#' + tag}</span> ))} </div> ) } // App.jsx: const list = ['HTML','CSS','JS','REACT']; <Tag tags={list}/>
key에는 목록에서 겹치지 않는 고유값을 넣어야 한다. 여기선 태그 문자열 자체가 유일해서 key={tag}로 썼다.4. MUI 라이브러리 — 미리 만들어진 부품 (27)
버튼·입력창 같은 UI를 직접 스타일링하지 않고, Material UI(MUI)에서 완성된 컴포넌트를 import해서 가져다 썼다. onClick 이벤트도 그대로 붙는다.
// components/27/SubmitButton.jsx import {AppBar, Button, TextField} from '@mui/material' const SubmitButton = () => { return ( <> <AppBar ...>메뉴</AppBar> <TextField></TextField> <Button variant='contained' onClick={() => alert('안녕하세요')}>버튼</Button> </> ) }
npm install @mui/material로 설치한 뒤 필요한 컴포넌트만 골라 import한다. 처음부터 CSS를 짜지 않아도 완성도 있는 UI가 나와서 편했다.5. 여러 컴포넌트 조립 — 레이아웃 나누기 (28)
한 화면을 Header · Menu · Content · Footer 네 조각으로 쪼개 각각 컴포넌트로 만들고, App.jsx에서 조립했다. 실제 웹페이지가 어떻게 부품 단위로 구성되는지 감이 왔다. Footer는 링크 배열을 props로 받아 map으로 그린다.
// components/28/Footer.jsx — 배열 props + map const Footer = ({contact}) => ( <footer> <ul> {contact.map((list) => ( <li><a key={list} href="#">{list}</a></li> ))} </ul> </footer> )
// App.jsx — 네 조각을 조립 const list = ['깃허브', '블로그', '링크드인']; <Header /> <Menu /> <Content /> <Footer contact={list}/>
6. useState — 바뀌는 값 다루기 (29)
오늘의 첫 번째 큰 산. 지금까지 화면은 한 번 그리면 끝이었는데, useState를 쓰면 값이 바뀔 때마다 화면이 자동으로 다시 그려진다. 카운터로 +1 / −1 / 초기화를 만들어봤다.
// components/29/Counter.jsx import { useState } from "react"; const Counter = () => { const [ count, setCount ] = useState(0) // [현재값, 바꾸는 함수] return ( <> <button onClick={() => setCount(c => c+1)}>+1</button> <button onClick={() => setCount(c => c-1)}>-1</button> <button onClick={() => setCount(0)}>초기화</button> <span>{count}</span> </> ) }
const [count, setCount] = useState(0) — 이게 오늘 가장 중요한 한 줄. count는 현재 상태값, setCount는 그 값을 바꾸는 함수다. 값을 직접 count = count+1로 바꾸면 안 되고 반드시 setCount로 바꿔야 화면이 갱신된다. setCount(c => c+1)처럼 이전 값 기반으로 바꾸는 게 안전하다고 배웠다.7. 폼 입력 다루기 — NameForm (32)
입력창에 타이핑하는 대로 아래 인사말이 실시간으로 바뀌게 만들었다. input의 value를 state에 묶고, onChange로 state를 갱신하는 제어 컴포넌트(controlled input) 패턴이다.
// components/32/NameForm.jsx const NameForm = () => { const [ name, setName ] = useState(''); return ( <> <input value={name} onChange={(e) => setName(e.target.value)} /> <p>안녕, {name}</p> </> ) }
e.target.value는 방금 입력한 글자. 타이핑 → onChange → setName → 화면 다시 그림, 이 흐름이 매 글자마다 돈다. 입력값을 state가 "쥐고 있는" 게 제어 컴포넌트의 핵심이다.8. props + state 결합 — ProductItem (33)
부모가 준 props(상품 이름)와 컴포넌트 내부의 state(담은 개수)를 한 컴포넌트에서 같이 썼다. 바뀌지 않는 값은 props로 받고, 바뀌는 값은 state로 관리한다는 역할 구분이 명확해졌다.
// components/33/ProductItem.jsx const ProductItem = ({name}) => { // name: props(고정) const [ count, setCount ] = useState(0) // count: state(변함) return ( <div> <h3>{name}</h3> <p>{count}담음</p> <button onClick={() => setCount(c => c+1)}>담기</button> </div> ) } // 사용: <ProductItem name="나이키 운동화" />
9. useEffect — 실행 타이밍 정하기 (34~37)
오늘의 두 번째 큰 산. useEffect는 "언제 이 코드를 실행할지"를 정하는 훅이다. 두 번째 인자(의존성 배열)에 따라 실행 시점이 완전히 달라진다 — 이걸 네 가지 경우로 나눠 실습했다.
| 의존성 배열 | 실행 시점 | 실습 |
|---|---|---|
[] 빈 배열 | 화면 뜰 때 딱 1번 | 34 Hello |
[] + cleanup | 1번 실행 + 정리 함수 | 35 Clock |
[count] | 그 값이 바뀔 때마다 | 36 Counter |
| 배열 생략 | 렌더링 될 때마다 | 37 Every |
① 처음 1번만 — 빈 배열 [] (34)
useEffect(() => { console.log("화면 뜰 때 딱 1번만 실행") }, []) // ← 의존성 배열이 비어 있으면 최초 1회
② 1번 실행 + 정리(cleanup) — 시계 (35)
setInterval로 1초마다 시간을 갱신하고, 화면에서 사라질 때 타이머를 정리하도록 return에 정리 함수를 넣었다. 정리를 안 하면 타이머가 계속 쌓인다.
useEffect(() => { const timer = setInterval(() => { setTime(new Date().toLocaleTimeString()) }, 1000) return () => clearInterval(timer) // ← 정리(cleanup) }, [])
③ 값이 바뀔 때마다 — [count] (36)
useEffect(() => { console.log('count바뀜:', count) }, [count]) // ← count가 바뀔 때만 실행
④ 매 렌더링마다 — 배열 생략 (37)
useEffect(() => { console.log('렌더링 될 때 마다 실행') }) // ← 두 번째 인자 없음
[]면 처음 1번, [값]이면 그 값이 바뀔 때, 아예 없으면 매번. return에 넣은 함수는 "치우는 코드"(cleanup)로, 타이머·이벤트 정리에 쓴다.10. 데이터 가져오기 — fetch (38~39)
오늘의 마지막 산. useEffect(처음 1번) 안에서 fetch로 외부 API 데이터를 받아 state에 담고, 그걸 map으로 화면에 그렸다. 지금까지 배운 useState + useEffect + map이 전부 한 곳에서 만난다.
① 사용자 목록 — JSONPlaceholder (38)
// components/38/Users.jsx const [ users, setUsers ] = useState([]) useEffect(() => { fetch('https://jsonplaceholder.typicode.com/users') .then((res) => res.json()) .then((data) => setUsers(data)) .catch((error) => console.error('데이터 로딩 실패:', error)) }, []) // 받아온 users를 map으로 목록 렌더링
② 서울 기온 — Open-Meteo + 로딩 상태 (39)
데이터가 오기 전/후를 구분하려고 isLoading state를 하나 더 뒀다. 불러오는 중엔 "불러오는 중...", 오면 기온을 보여준다.
// components/39/Weather.jsx const [ temp, setTemp ] = useState(null) const [ isLoading, setIsLoading ] = useState(true) useEffect(() => { fetch('https://api.open-meteo.com/v1/forecast?...¤t_weather=true') .then((res) => res.json()) .then((data) => { setTemp(data.current_weather.temperature); setIsLoading(false) }) .catch((error) => { console.error('기온 로딩 실패:', error); setIsLoading(false) }) }, []) // {isLoading ? '불러오는 중...' : temp + '°C'}
fetch → .then(res => res.json()) → .then(data => set...) → .catch로 실패 대비. API 호출은 화면이 처음 뜰 때 1번만 하면 되니까 useEffect(..., []) 안에 넣는다. 로딩 상태(isLoading)를 따로 두면 "불러오는 중" 표시를 깔끔하게 처리할 수 있다.11. 오늘 배운 것 한눈에
| 주제 | 실습(컴포넌트) | 핵심 |
|---|---|---|
| 객체 매핑 | 24 Alert | map[type]으로 설정값 꺼내기 (중첩 객체) |
| 구조 분해 · 스프레드 | CLI 질문 | {a,b}=obj / [...arr] |
| 리스트 렌더링 | 25·26 Rating·Tag | map + key |
| MUI | 27 SubmitButton | 완성된 UI 부품 import |
| 컴포넌트 조립 | 28 Header~Footer | 레이아웃을 조각으로 분리 |
| useState | 29·32·33 | 카운터 · 폼 입력 · props+state |
| useEffect | 34~37 | [] / [값] / 생략 + cleanup |
| fetch | 38·39 | API 데이터 → state → map |
🔁 회고
useState와 useEffect를 만졌다. "데이터를 바꾸면 화면이 알아서 다시 그려진다"는 걸 카운터·시계·fetch로 직접 확인하니, 어제 배운 "React를 왜 쓰는지"가 완전히 체감됐다. 헷갈린 구조 분해·스프레드는 넘어가지 않고 Claude CLI에 물어 코드 주석으로 정리해둔 게 특히 잘한 것 같다.
useEffect의 의존성 배열([] vs [값] vs 생략)이 헷갈려서 계속 표를 다시 봤다. cleanup(정리 함수)이 왜 필요한지는 시계 예제에서 겨우 감을 잡았는데, 아직 완전히 내 것은 아니다. fetch의 .then().then().catch() 체인도 순서를 외우기보단 흐름으로 이해하는 연습이 더 필요할 것 같다.
✍️ 마치며
어제가 "컴포넌트를 만들어 조립"하는 정적인 React였다면, 오늘은 useState·useEffect·fetch로 움직이고 데이터를 다루는 React를 배웠다. react.html 진도표 기준으로 39번 Weather까지 한 바퀴를 다 돈 셈이다. 다음엔 오늘 배운 걸 모아 작은 앱(예: 실시간 데이터를 쓰는 대시보드)을 직접 만들어보며 손에 익히고 싶다.
🏷️ React | useState | useEffect | fetch | map | 구조분해 | 스프레드 | 학습일지
'AI 기반 프론트엔드 웹개발자 양성과정' 카테고리의 다른 글
| [260714 12일차] Node·Express, Groq API (0) | 2026.07.14 |
|---|---|
| [260713 11일차] Mini Project #3 (1) | 2026.07.13 |
| [260709 09일차] React (0) | 2026.07.09 |
| [260708수 08일차] Mini-Project #2 (0) | 2026.07.08 |
| [260707화 07일차] JavaScript (0) | 2026.07.08 |