6_Hook
코드 블록의 Try it Yourself 버튼으로 직접 실행할 수 있다.
구문
1. Hook
Hook은 함수형 컴포넌트에서 React 기능을 사용할 수 있게 해주는 특별한 함수다.
이름이 항상 use로 시작하는 것이 특징이다.
1.1. Hook 사용 규칙
Hook을 사용할 때 반드시 지켜야 할 두 가지 규칙이 있다.
| 규칙 | 설명 |
|---|---|
| 컴포넌트 최상위에서만 호출 | if문, for문, 중첩 함수 안에서 호출하면 안 된다 |
| React 함수에서만 호출 | 일반 JavaScript 함수에서는 사용할 수 없다 |
// ❌ 잘못된 예시
function App() {
if (condition) {
const [count, setCount] = useState(0); // if문 안에서 호출 → 오류!
}
}
// ✅ 올바른 예시
function App() {
const [count, setCount] = useState(0); // 최상위에서 호출
}
1.2. 자주 쓰는 Hook 목록
| Hook | 용도 |
|---|---|
useState | 상태(데이터) 관리 |
useEffect | 화면 표시/사라짐/업데이트 시 실행 |
useRef | DOM 요소 직접 접근, 렌더링 없이 값 저장 |
useContext | 컴포넌트 트리 전체에 데이터 전달 |
useMemo | 복잡한 계산 결과 캐싱 (성능 최적화) |
useCallback | 함수 캐싱 (성능 최적화) |
이 챕터에서는 가장 많이 쓰는 useState와 useEffect를 집중적으로 다룬다.
2. UseState()
2.1. State란?
State는 컴포넌트의 데이터 저장소다. 값이 변경되면 화면이 자동으로 다시 그려진다.
2.1.1. 기본 사용법
import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
return (
<div>
<p>횟수: {count}</p>
<button onClick={() => setCount(count + 1)}>증가</button>
</div>
);
}
2.2. useState 동작 원리
useState는 두 개의 값을 배열로 반환한다.
- 첫 번째: 현재 상태 값
- 두 번째: 상태를 변경하는 함수
const [value, setValue] = useState(초기값);
2.2.1. 예제: 카운터
function Counter() {
const [num, setNum] = useState(0);
const plus = () => setNum(num + 1);
const minus = () => setNum(num - 1);
return (
<div>
<h2>{num}</h2>
<button onClick={plus}>+</button>
<button onClick={minus}>-</button>
</div>
);
}
2.3. 객체와 배열 State 관리
⚠️WARNING
주의: 직접 수정 금지!
상태 객체나 배열을 직접 수정하면 안 된다. 반드시 새로운 객체/배열을 만들어야 한다.
전개 연산자 를 사용하면 편리하다.
2.3.1. 예제 2: Todo
/* 전체 영역 설정 */
.container {
max-width: 400px;
margin: 50px auto;
padding: 20px;
border: 1px solid #ddd;
border-radius: 8px;
}
/* 입력창과 버튼 가로 배치 */
.input-box {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
/* 입력창 크기 확장 */
.input-field {
flex: 1;
padding: 10px;
}
/* 추가 버튼 디자인 */
.add-btn {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
cursor: pointer;
}
/* 목록 스타일 초기화 */
.list-box {
list-style: none;
padding: 0;
}
/* 개별 항목 디자인 */
.list-item {
padding: 10px;
border-bottom: 1px solid #eee;
}
3. useEffect()
컴포넌트내의 함수가 호출되는 시점을 제어하는 훅
useEffect는 컴포넌트가 화면에 나타날 때, 사라질 때, 또는 특정 값이 변할 때 실행할 코드를 정의한다.
3.1. 문법정리
| 상황 | 예시 |
|---|---|
| 첫 렌더링 시 실행 (마운트) | useEffect(() => { console.log("마운트");}, []) |
| 렌더링될 때마다 실행 | useEffect(() => { console.log("렌더링");}) |
| 특정 값 변경 시 실행 | useEffect(() => { console.log(값);}, [값]) |
| 컴포넌트 제거 시 정리 (언마운트) | useEffect(() => { console.log("마운트"); return () => console.log("언마운트");}, []) |
| 값 변경 시 실행 + 이전 것 정리 | useEffect(() => { console.log(값); return () => console.log("정리");}, [값]) |
3.1.1. 마운트시
컴포넌트가 화면에 처음 나타날 때 단 한 번 실행된다.
빈 배열 []을 의존성 배열로 전달한다.
useEffect(() => {
console.log("컴포넌트가 화면에 나타남");
}, []);
3.1.2. 렌더링시
의존성 배열을 생략하면 렌더링될 때마다 실행된다. 상태나 props가 바뀔 때마다 매번 호출되므로 주의해서 사용한다.
useEffect(() => {
console.log("렌더링됨");
});
3.1.3. 특정값 변경시
의존성 배열에 값을 넣으면 그 값이 바뀔 때마다 실행된다.
const [count, setCount] = useState(0);
useEffect(() => {
console.log("count가 바뀜:", count);
}, [count]);
3.1.4. 언마운트시
return으로 **정리 함수(cleanup)**를 반환하면 컴포넌트가 화면에서 사라질 때 실행된다.
타이머, 이벤트 리스너 등 정리가 필요한 경우에 사용한다.
useEffect(() => {
console.log("마운트");
return () => {
console.log("언마운트 - 정리 실행");
};
}, []);
3.2. 실습: 로그인
웹브라우저의 데이터 저장 공간인 로컬 스토리지에 로그인 정보를 저장하는 로그인 페이지를 만들어보자
.container {
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
}
.card {
background: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.26);
border-radius: 10px;
padding: 2rem;
margin: 2rem auto;
width: 90%;
max-width: 40rem;
text-align: center;
}
.control {
margin: 1rem 0;
display: flex;
flex-direction: column;
text-align: left;
}
.control label {
font-weight: bold;
margin-bottom: 0.5rem;
}
.control input {
font: inherit;
padding: 0.5rem;
border-radius: 6px;
border: 1px solid #ccc;
}
.actions {
text-align: center;
margin-top: 1rem;
}
.btn {
font: inherit;
background: #7a0141;
border: 1px solid #7a0141;
color: white;
padding: 0.75rem 2rem;
border-radius: 30px;
cursor: pointer;
}
.btn:disabled {
background: #ccc;
border-color: #ccc;
color: #666;
cursor: not-allowed;
}
.btn:hover:not(:disabled) {
background: #a40256;
border-color: #a40256;
}
3.3. 코드 분석
3.3.1. 상태(State) 선언
const [isLoggedIn, setIsLoggedIn] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
| 변수 | 초기값 | 역할 |
|---|---|---|
isLoggedIn | false | 로그인 여부 — true면 홈 화면, false면 로그인 화면 |
email | "" | 이메일 입력창의 현재 값 |
password | "" | 비밀번호 입력창의 현재 값 |
3.3.2. useEffect — 재방문 체크
useEffect(() => {
const storedLoginInfo = localStorage.getItem("isLoggedIn");
if (storedLoginInfo === "1") {
setIsLoggedIn(true);
}
}, []);
localStorage는 브라우저에 데이터를 저장하는 공간이다. 새로고침해도 사라지지 않는다.getItem("isLoggedIn")으로 이전에 저장된 로그인 정보를 꺼낸다.- 값이
"1"이면 이미 로그인한 사용자이므로setIsLoggedIn(true)로 상태를 바꾼다. - 의존성 배열이
[]이므로 처음 마운트될 때 한 번만 실행된다 → 무한 루프 없음.
3.3.3. loginHandler — 로그인 처리
const loginHandler = (e) => {
e.preventDefault();
localStorage.setItem("isLoggedIn", "1");
setIsLoggedIn(true);
};
e.preventDefault(): 폼 제출 시 페이지가 새로고침되는 기본 동작을 막는다.localStorage.setItem("isLoggedIn", "1"): 브라우저에 로그인 정보를 저장한다. 재방문해도 로그인 상태가 유지된다.setIsLoggedIn(true): 상태를 바꿔 화면을 홈으로 전환한다.
3.3.4. logoutHandler — 로그아웃 처리
const logoutHandler = () => {
localStorage.removeItem("isLoggedIn");
setIsLoggedIn(false);
setEmail("");
setPassword("");
};
localStorage.removeItem("isLoggedIn"): 저장된 로그인 정보를 삭제한다.setIsLoggedIn(false): 상태를 바꿔 화면을 로그인 폼으로 전환한다.setEmail(""),setPassword(""): 입력창을 빈 값으로 초기화한다.
3.3.5. JSX — 화면 분기
{!isLoggedIn ? (
<로그인 폼 />
) : (
<환영 메시지 />
)}
isLoggedIn이false이면!isLoggedIn이true가 되어 로그인 폼을 보여준다.isLoggedIn이true이면 환영 화면을 보여준다.?:은 삼항 연산자로,if/else를 한 줄로 표현한 것이다.