프론트엔드 스터디 11주차: React 기초 2 —.
useEffect로 사이드 이펙트를 다루고, 이벤트 처리와 폼 상태 관리까지 — React로 실제 동작하는 앱을 만들기 위한 핵심 기술을 배웁니다.
<strong>학습 챕터:</strong> 한 입 크기로 잘라먹는 리액트 — 라이프 사이클 /
useEffect
<strong>자료 바로가기:</strong>
---
---
import { useState, useEffect } from "react";
function UserProfile({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
// 이 함수는 렌더링 이후에 실행됨
fetch(`/api/users/${userId}`)
.then((res) => res.json())
.then((data) => setUser(data));
}, [userId]); // ← 의존성 배열: userId가 바뀔 때마다 재실행
if (!user) return <p>로딩 중...</p>;
return <h1>{user.name}</h1>;
}// 패턴 1. 빈 배열 [] — 마운트 시 한 번만 실행
useEffect(() => {
console.log("컴포넌트가 처음 나타났을 때 한 번만");
}, []);
// 패턴 2. 값이 있는 배열 [dep] — dep이 바뀔 때마다 실행
useEffect(() => {
console.log("userId가 바뀔 때마다 새 데이터를 가져옴");
}, [userId]);
// 패턴 3. 배열 없음 — 매 렌더링마다 실행 (거의 안 씀)
useEffect(() => {
console.log("렌더링할 때마다 — 주의: 성능 문제 가능");
});useEffect(() => {
// 설정(setup)
const timer = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
// 클린업(cleanup) — 컴포넌트가 사라지거나 의존성이 바뀔 때 실행
return () => {
clearInterval(timer);
};
}, []);// ❌ 잘못된 방법 — useEffect 콜백을 async로 만듦
useEffect(async () => {
const data = await fetch("/api/data");
// React가 반환된 Promise를 클린업 함수로 인식하려고 시도
}, []);
// ✅ 올바른 방법 — 내부에 async 함수를 정의하고 호출
useEffect(() => {
async function fetchData() {
try {
const res = await fetch("/api/data");
const data = await res.json();
setData(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
fetchData();
}, []);function PostList() {
const [posts, setPosts] = useState([]);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
async function loadPosts() {
try {
const res = await fetch("/api/posts");
if (!res.ok) throw new Error("서버 오류");
const data = await res.json();
setPosts(data);
} catch (err) {
setError(err.message);
} finally {
setLoading(false);
}
}
loadPosts();
}, []);
if (loading) return <p>로딩 중...</p>;
if (error) return <p>에러: {error}</p>;
return (
<ul>
{posts.map((post) => (
<li key={post.id}>{post.title}</li>
))}
</ul>
);
}---
function ButtonDemo() {
// 이벤트 핸들러 함수 정의
function handleClick() {
alert("버튼이 클릭되었습니다!");
}
return (
<div>
{/* ✅ 함수 참조 전달 — 클릭할 때 실행 */}
<button onClick={handleClick}>클릭</button>
{/* ✅ 인라인 화살표 함수 — 인자가 필요할 때 */}
<button onClick={() => alert("안녕!")}>인사</button>
{/* ❌ 함수 호출 — 렌더링 시 즉시 실행됨! */}
<button onClick={handleClick()}>이건 잘못됨</button>
</div>
);
}function InputDemo() {
function handleChange(e) {
// e.target — 이벤트가 발생한 DOM 요소
console.log(e.target.value); // 입력된 텍스트
console.log(e.target.name); // input의 name 속성
}
function handleSubmit(e) {
// 폼 제출 시 페이지 새로고침 방지
e.preventDefault();
console.log("폼 제출됨");
}
return (
<form onSubmit={handleSubmit}>
<input name="username" onChange={handleChange} />
<button type="submit">제출</button>
</form>
);
}<strong>onClick</strong> — 클릭 이벤트 (버튼, 링크 등) <strong>onChange</strong> — 값 변경 이벤트 (input, select, textarea) <strong>onSubmit</strong> — 폼 제출 이벤트 <strong>onFocus / onBlur</strong> — 포커스 진입 / 이탈 <strong>onKeyDown / onKeyUp</strong> — 키보드 입력
---
function NameInput() {
const [name, setName] = useState("");
return (
<div>
{/* value로 표시할 값 지정, onChange로 변경 감지 */}
<input
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="이름을 입력하세요"
/>
<p>입력된 이름: {name}</p>
</div>
);
}function LoginForm() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
function handleSubmit(e) {
e.preventDefault(); // 페이지 새로고침 방지
// 간단한 유효성 검사
if (!email.includes("@")) {
setError("올바른 이메일 형식이 아닙니다");
return;
}
if (password.length < 6) {
setError("비밀번호는 6자 이상이어야 합니다");
return;
}
setError("");
console.log("로그인 시도:", { email, password });
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="이메일"
/>
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
placeholder="비밀번호"
/>
{error && <p style={{ color: "red" }}>{error}</p>}
<button type="submit">로그인</button>
</form>
);
}function SignupForm() {
const [form, setForm] = useState({
name: "",
email: "",
password: "",
});
// name 속성을 기준으로 해당 필드만 업데이트
function handleChange(e) {
const { name, value } = e.target;
setForm((prev) => ({ ...prev, [name]: value }));
// [name]은 계산된 프로퍼티 이름 — name 변수의 값이 키가 됨
}
function handleSubmit(e) {
e.preventDefault();
console.log("가입 정보:", form);
}
return (
<form onSubmit={handleSubmit}>
<input name="name" value={form.name} onChange={handleChange} />
<input name="email" value={form.email} onChange={handleChange} />
<input
name="password"
type="password"
value={form.password}
onChange={handleChange}
/>
<button type="submit">가입</button>
</form>
);
}---
import { useState, useEffect } from "react";
function TodoApp() {
const [todos, setTodos] = useState([]); // 할 일 목록 (배열 state)
const [input, setInput] = useState(""); // 입력 필드 (문자열 state)
// 마운트 시 localStorage에서 데이터 불러오기 (useEffect)
useEffect(() => {
const saved = localStorage.getItem("todos");
if (saved) {
setTodos(JSON.parse(saved));
}
}, []);
// todos가 바뀔 때마다 localStorage에 저장 (useEffect + 의존성)
useEffect(() => {
localStorage.setItem("todos", JSON.stringify(todos));
}, [todos]);
// 폼 제출 이벤트 처리
function handleSubmit(e) {
e.preventDefault(); // 기본 동작 방지
if (!input.trim()) return; // 빈 입력 방지
// 스프레드로 불변 업데이트
setTodos((prev) => [...prev, { id: Date.now(), text: input, done: false }]);
setInput(""); // 입력 필드 초기화
}
// 완료 토글
function toggleTodo(id) {
setTodos((prev) =>
prev.map((todo) =>
todo.id === id ? { ...todo, done: !todo.done } : todo,
),
);
}
// 삭제
function deleteTodo(id) {
setTodos((prev) => prev.filter((todo) => todo.id !== id));
}
return (
<div>
<h1>할 일 목록</h1>
{/* Controlled Component — value + onChange */}
<form onSubmit={handleSubmit}>
<input
value={input}
onChange={(e) => setInput(e.target.value)}
placeholder="할 일을 입력하세요"
/>
<button type="submit">추가</button>
</form>
{/* 리스트 렌더링 — map + key */}
<ul>
{todos.map((todo) => (
<li key={todo.id}>
<span
onClick={() => toggleTodo(todo.id)}
style={{
textDecoration: todo.done ? "line-through" : "none",
cursor: "pointer",
}}
>
{todo.text}
</span>
<button onClick={() => deleteTodo(todo.id)}>삭제</button>
</li>
))}
</ul>
</div>
);
}---
`function Timer() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);---
---
---
프론트엔드 스터디 10주차 학습 자료: React 기초 1 — 컴포넌트, JSX, props, useState
Post Q&A
프론트엔드 스터디 11주차: React 기초 2 — useEffect, 이벤트 처리, 폼 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
지금 당장 읽지 않아도 됩니다. React를 배우면서 에러 처리가 필요해지거나 면접 대비가 필요할 때 참고하세요.