리액트를 공부하시다보면 useState, useRef, useEffect...처럼 use로 시작하는 함수들을 굉장히 많이 볼 수 있는데요, 이런 함수들을 Hooks라고 부릅니다.

리액트에서 컴포넌트를 작성할 때는 흔히 아래와 같은 함수형 컴포넌트를 사용합니다.
아래는 버튼을 누르면 숫자가 1 증가하는 컴포넌트의 코드입니다.
(참고로 5번 누른 이후 상황입니다)
import { useState } from "react";
export default function Count() {
const [number, setNumber] = useState(0);
return (
<>
<h1>{number}</h1>
<button
onClick={() => {
setNumber(number + 1);
}}
>
+1
</button>
</>
);
}
그런데 클래스 형식으로도 컴포넌트를 작성할 수 있어요.
그걸 클래스 컴포넌트라 하는데, 함수 컴포넌트에 비해 문법이 많이 복잡하고 가독성도 떨어집니다.
import React, { Component } from "react";
export default class Count extends Component {
constructor(props) {
super(props);
this.state = {
number: 0,
};
}
increment = () => {
this.setState((prevState) => ({
number: prevState.number + 1,
}));
};
render() {
return (
<>
<h1>{this.state.number}</h1>
<button onClick={this.increment}>+1</button>
</>
);
}
}
놀랍게도 기존에는 useState, useRef 같은 함수들을 클래스 컴포넌트에서만 쓸 수 있었어요.
하지만 Hooks가 등장하면서 이러한 함수들을 함수 컴포넌트에서도 쓸 수 있게 됐습니다.
클래스 컴포넌트의 기능을 갈고리로 낚아 채온다... 는 뜻에서 Hook이라는 이름이 붙었어요.
모든 Hook은 이름 앞에 use가 옵니다. 그러니까
useState는 클래스 컴포넌트의 State 기능을 낚아채온 거고useRef는 클래스 컴포넌트의 Ref 기능을 낚아채온 거다라고 생각하시면 됩니다.
Hook을 사용할 땐 이런 규칙을 지켜야 합니다.
(1) 함수형 컴포넌트 혹은 Custom Hook 안에서만 사용할 수 있다
const [number, setNumber] = useState(0);
export default function Count() {
// 생략
}
즉 이렇게 useState를 Count 바깥에서 사용하시면 안 됩니다.
커스텀 훅이 뭔지는 뒤에서 설명할게요.
(2) 조건문 / 반복문 안에서 사용할 수 없다
if (true){
const [number, setNumber] = useState(0);
}
기본적으로 조건문, 반복문은 해당 조건이 만족되어야만 안에 내용을 실행하는 특징이 있습니다.
따라서 이 안에다 Hook을 사용하면, 어떤 렌더링에서는 실행되고 어떤 렌더링에선 실행되지 않아 호출 순서가 꼬이게 됩니다.
규칙을 어기시면 이런 무시무시한 경고창을 개발자도구에서 보실 수 있습니다. 바로 사이트가 뻗진 않지만 잠재적으로 고치기 어려운 버그가 생길 수 있습니다.
useState vs useRefuseState에 대해선 앞선 글에서 잘 정리했으니 참고 바랍니다
useState의 state, useRef의 ref 모두 컴포넌트 내부 변수로 활용 가능하다는 공통점이 있습니다. 그 뜻은 컴포넌트가 리렌더링되어도 값이 유지된다는 뜻입니다.
참고로 컴포넌트는 이러한 상황에서 리렌더링이 됩니다.
let으로 변수를 선언하면, 컴포넌트가 리렌더링되면 코드가 다시 실행되어 변수 선언이 다시 이루어집니다. 즉 매번 초기값으로 리셋되는 문제가 있습니다.
state는 변경될 때마다 컴포넌트가 리렌더링되지만, ref는 변경되어도 컴포넌트가 리렌더링되지 않습니다.

import React, { useState, useRef } from 'react';
function Counter() {
const [stateCount, setStateCount] = useState(0);
const refCount = useRef(0);
let letCount = 0;
console.log('컴포넌트가 리렌더링됨');
return (
<div>
<h2>State count: {stateCount}</h2>
<button onClick={() => setStateCount(stateCount + 1)}>State 증가</button>
<h2>Ref count: {refCount.current}</h2>
<button onClick={() => {
refCount.current += 1;
console.log('Ref count:', refCount.current);
}}>Ref 증가</button>
</div>
);
}
export default Counter;
State 증가를 누를 시setCount(count + 1) 호출 후 컴포넌트가 리렌더링되어, 화면에 숫자 즉시 갱신됩니다.Ref 증가를 누를 시countRef.current+ 호출 시 값은 증가하지만, 컴포넌트가 리렌더링되진 않아 화면에 표시되진 않습니다.console.log(countRef.current)에서는 증가된 값을 확인할 수 있습니다.State 증가를 나중에 누르면 컴포넌트가 리렌더링되니, 그제서야 증가된 값이 반영됩니다.useRef를 통한 DOM 요소 조작useRef는 DOM 요소 조작에도 흔히 사용됩니다.
특히 특정 DOM 요소에의 자동 포커스나, 특정 DOM 요소로의 자동 스크롤에 흔히 사용됩니다.
예를 들어, 회원가입창에서 Submit 버튼을 눌렀는데 이름이 미입력된 경우 이름을 강조하고 싶다고 합시다.

(코드의 간결함을 위해 이름 제외 다른 필드는 생략한 점 양해 바랍니다)
function Register() {
const [input, setInput] = useState({ name: "" });
const nameRef = useRef();
const onSubmit = () => {
if (input.name === "") {
nameRef.current.focus(); // DOM 직접 제어
}
};
return (
<>
<input
ref={nameRef}
type="text"
name="name"
value={input.name}
onChange={(e) => setInput({ name: e.target.value })}
placeholder="이름을 입력하세요"
/>
<button onClick={onSubmit}>제출</button>
</>
);
}
이렇게 input 태그의 ref 속성에 useRef로 만든 inputRef를 명시해 주고, 제출버튼을 눌렀을 때 실행되는 onSubmit 이벤트에서 inputRef.current.focus()를 주게끔 하는 식으로 조작이 가능합니다.
useEffectuseEffect는 컴포넌트의 Side Effect를 처리하는 Hook입니다.
Side Effect...가 뭐냐면, 컴포넌트의 핵심 기능은 화면에 보여지는 렌더링이겠죠. 그런데 실제로는 이와 더불어 다른 작업을 수행할 수 있습니다.
useEffect는 (1)콜백함수 (effect 실행 내용)와 (2)의존성 배열을 받습니다.
// 콜백함수, 배열
// count 또는 input 값이 바뀌면, 콜백함수 실행
useEffect(() => {
console.log(`count: ${count} | input: ${input}`);
}, [count, input]);
이때 의존성 배열을 비워 두거나, 생략할 수도 있습니다.
[]), 컴포넌트가 처음 마운트될 때 1번만 실행직접 custom hook을 만드는 것 역시 가능합니다. 앞서 말했듯이 Hook은 함수형 컴포넌트나 Custom Hook 내부에서만 사용할 수 있습니다. 즉 중복되는 Hook 코드가 반복될 때 함수화하고 싶으시면 Custom Hook을 사용하셔야 합니다.
리액트는 use로 시작하는 함수를 만들면 자동으로 Hook으로 인식하기 때문에, 쉽게 만드실 수 있습니다.
아래 코드처럼 여러 입력창에서 동일한 입력 관리 로직을 사용하는 경우, Custom Hook으로 분리할 수 있겠죠.
// src/hooks/useInput.jsx
import { useState } from "react";
// Custom Hook 만들기
// 그냥 함수 이름이 use로 시작하면 됨
export default function useInput() {
const [input, setInput] = useState("");
const onChange = (e) => {
setInput(e.target.value);
};
return [input, onChange];
}
// src/components/HookExam.jsx
import useInput from "../hooks/useInput";
export default function HookExam() {
const [name, onChangeName] = useInput("");
const [email, onChangeEmail] = useInput("");
return (
<div>
<input value={name} onChange={onChangeName} placeholder="이름" />
<input value={email} onChange={onChangeEmail} placeholder="이메일" />
</div>
);
}