🐨CoalaCoding
Docs▾
JavaScriptReactHTML & CSSBackendAI & LLMDev ToolsCreative
B반1
👾숏츠
🙉B반2
게시판
🐨CoalaCoding

개발자를 위한 한국어 웹 기술 문서

문서

  • JavaScript
  • React
  • HTML & CSS
  • Backend
  • AI & LLM
  • Dev Tools
  • Creative

커뮤니티

  • 게시판
  • 예제 모음

기타

  • 관리자

정책

  • 소개
  • 개인정보처리방침
  • 이용약관
  • 연락처
© 2026 CoalaCoding. All rights reserved.
  • React 학습 전 필수 JavaScript 문법
  • 1. React 소스 코드 수정, 배포 및 렌더링
  • 2. 시작
  • 2. JSX 문법과 컴포넌트
  • 3. react-vite-설치
  • 3. 이벤트 처리
  • 4. 노코드리액트
  • 4. Hook
  • 5. csrssrreact-렌더링-참조글
  • 5. useContext로 전역 상태 관리
  • 6. 컴포넌트
  • 6. CSS 리액트에 넣는 5가지 방법
  • 7. 시작하기
  • 7. Props (속성)
  • 8. 2-scaffolding-react-project
  • 8. React Router
  • 9. props
  • 9. React 개발 환경 이해
  • 10. 3-웹문서-작성하기
  • 11. 컴포넌트
  • 12. map과컴포넌트
  • 13. event
  • 14. 4-리액트로-변경하기
  • 15. 컴포넌트-데이터관리
  • 16. composition
  • 17. 앱제작-준비
  • 21. 환경설정
  • 24. 컴포넌트-전략
  • 25. 리액트-네이버-로그인
  • 26. 리액트교안
  1. 홈
  2. 문서
  3. React
  4. React 기초
  5. 4. Hook

4. Hook

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화면 표시/사라짐/업데이트 시 실행
useRefDOM 요소 직접 접근, 렌더링 없이 값 저장
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.0.0.0.1. 전개연산자를 사용하여 객체를 수정
const user = { name: "철수", age: 20 };
const newUser = { ...user, name: "영희" };
2.3.0.0.0.2. 전개연산자를 사용하지 않고 객체를 수정
const user = { name: "철수", age: 20 };
const newUser = { name: "영희", age: user.age };
2.3.0.0.0.3. 전개연산자를 사용하여 배열을 수정
const fruits = ["사과", "바나나"];
const newFruits = [...fruits, "오렌지"];
2.3.0.0.0.4. 전개연산자를 사용하지 않고 배열을 수정
const fruits = ["사과", "바나나"];
const newFruits2 = fruits.concat("오렌지");

2.3.1. 예제 2: Todo

Todo.css

/* 전체 영역 설정 */
.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;
}

Todo.jsx(추가)

추가

```jsx
import { useState } from "react";

function Todo() {
  const [todos, setTodos] = useState([]);
  const [inputValue, setInputValue] = useState("");

  const addTodo = () => {
    if (inputValue.trim() === "") return;
    setTodos([...todos, inputValue]);
    setInputValue("");
  };

  return (
    <div className="container">
      <div className="input-box">
        <input
          className="input-field"
          value={inputValue}
          onChange={(e) => setInputValue(e.target.value)}
          placeholder="할 일을 입력하세요"
        />
        <button className="add-btn" onClick={addTodo}>추가</button>
      </div>
      <ul className="list-box">
        {todos.map((todo, index) => (
          <li key={index} className="list-item">{todo}</li>
        ))}
      </ul>
    </div>
  );
}
```

- 상태 관리 변수: todos는 할 일 목록 배열을, inputValue는 입력창의 문자열을 관리한다.
- 상태 갱신 함수: addTodo 함수 내부에서 전개 구문(...todos)을 사용하여 기존 배열을 복사한 후 새 항목을 추가한다.

Todo.jsx(삭제)

삭제 기능이 포함된 리스트

```jsx
const deleteTodo = (index) => {
  setTodos(todos.filter((_, i) => i !== index));
};

return (
  <ul>
    {todos.map((todo, index) => (
      <li key={index}>
        {todo} <button onClick={() => deleteTodo(index)}>삭제</button>
      </li>
    ))}
  </ul>
);
```

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. 실습: 로그인

웹브라우저의 데이터 저장 공간인 로컬 스토리지에 로그인 정보를 저장하는 로그인 페이지를 만들어보자

App.css



.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;
}


App.jsx

import { useState, useEffect } from "react";
import "./App.css";

const App = () => {
  const [isLoggedIn, setIsLoggedIn] = useState(false);
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");

  useEffect(() => {
    const storedLoginInfo = localStorage.getItem("isLoggedIn");
    if (storedLoginInfo === "1") {
      setIsLoggedIn(true);
    }
  }, []);

  const loginHandler = (e) => {
    e.preventDefault();
    localStorage.setItem("isLoggedIn", "1");
    setIsLoggedIn(true);
  };

  const logoutHandler = () => {
    localStorage.removeItem("isLoggedIn");
    setIsLoggedIn(false);
    setEmail("");
    setPassword("");
  };

  return (
    <div className="container">
      <header className="main-header">
        <h1>React Auth Demo</h1>
        {isLoggedIn && (
          <nav className="nav">
            <button className="btn" onClick={logoutHandler}>
              로그아웃
            </button>
          </nav>
        )}
      </header>
      <main>
        {!isLoggedIn ? (
          <div className="card login">
            <form onSubmit={loginHandler}>
              <div className="control">
                <label htmlFor="email">이메일</label>
                <input type="email" id="email" value={email} onChange={(e) => setEmail(e.target.value)} />
              </div>
              <div className="control">
                <label htmlFor="password">비밀번호</label>
                <input type="password" id="password" value={password} onChange={(e) => setPassword(e.target.value)} />
              </div>
              <div className="actions">
                <button type="submit" className="btn">
                  로그인
                </button>
              </div>
            </form>
          </div>
        ) : (
          <div className="card home">
            <h1>환영합니다!</h1>
            <p>로그인에 성공했습니다.</p>
          </div>
        )}
      </main>
    </div>
  );
};

export default App;


3.3. 코드 분석

3.3.1. 상태(State) 선언

const [isLoggedIn, setIsLoggedIn] = useState(false);
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
변수초기값역할
isLoggedInfalse로그인 여부 — 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를 한 줄로 표현한 것이다.

목차

  • 1.1. Hook 사용 규칙
  • 1.2. 자주 쓰는 Hook 목록
  • 2.1. State란?
  • 2.1.1. 기본 사용법
  • 2.2. useState 동작 원리
  • 2.2.1. 예제: 카운터
  • 2.3. 객체와 배열 State 관리
  • 2.3.1. 예제 2: Todo
  • 3.1. 문법정리
  • 3.1.1. 마운트시
  • 3.1.2. 렌더링시
  • 3.1.3. 특정값 변경시
  • 3.1.4. 언마운트시
  • 3.2. 실습: 로그인
  • 3.3. 코드 분석
  • 3.3.1. 상태(State) 선언
  • 3.3.2. useEffect — 재방문 체크
  • 3.3.3. loginHandler — 로그인 처리
  • 3.3.4. logoutHandler — 로그아웃 처리
  • 3.3.5. JSX — 화면 분기