React Hooks는 Class 컴포넌트에서만 사용할 수 있었던 React의 특수한 기능들을 Function 컴포넌트에서도 사용할 수 있게 해주는 메서드들을 말합니다.
2017년 이전의 React에서는 대부분의 사람들이 클래스 컴포넌트를 사용했습니다. 물론 당시에도 함수 컴포넌트를 만들 수는 있었지만, 함수 컴포넌트는 단순히 UI를 렌더링하는 데만 사용할 수 있었고, React의 모든 기능을 활용할 수 없었습니다. 반면, 클래스 컴포넌트는 상태 관리와 생명주기 메서드 등 React의 모든 기능을 제공했기 때문에 주로 사용되었습니다.
그러나 클래스 컴포넌트에는 큰 단점이 하나 있었습니다. 클래스를 사용해야 했기 때문에, 함수 컴포넌트에 비해 문법이 훨씬 더 복잡했습니다.
이 때문에 많은 사람들은 간결한 문법을 가진 함수 컴포넌트에서도 React의 모든 기능을 사용할 수 있기를 원했습니다. 이러한 요구를 반영하여, 함수 컴포넌트에서도 클래스 컴포넌트의 기능을 가져와 사용할 수 있도록 돕는 React Hooks가 개발되었습니다.

그렇기 때문에 오늘날의 우리는 굳이 문법이 복잡한 클래스 컴포넌트를 쓸 필요가 없어졌습니다. 정말 특별한 상황이 아니라면 거의 모든 컴포넌트를 함수 컴포넌트로 만들게 되었습니다.
지난 시간까지 살펴봤던 useState나 useRef 같은 React의 내장 함수들은 전부 React Hooks 입니다. useState는 State 기능을 낚아채오는 Hook이었고 useRef는 Reference 기능을 낚아채오는 Hook이었습니다.

React Hooks에는 이름 앞에 use라는 접두사가 붙는 특징이 있습니다.

그리고 React Hook의 각각의 메서드들은 Hook이라고 부릅니다.

useState와 useRef 이외에도 useEffect나 useReducer 등등의 굉장히 다양한 Hook들이 존재합니다.

React Hook은 함수 컴포넌트 내부에서만 호출할 수 있습니다.

React Hook은 조건문이나 반복문 내부에서 호출할 수 없습니다.

use라는 접두사를 이용해서 커스텀 Hook도 만들 수 있습니다.

우선 HookExam.jsx 파일에 HookExam이라는 함수 컴포넌트를 생성합니다.
const HookExam = () => {
return <div>Hook Exam</div>;
};
export default HookExam;
useState라는 React Hook을 불러옵니다. 그런 다음, useState를 함수 컴포넌트 외부에서 선언해줍니다.
import { useState } from "react";
const state = useState();
const HookExam = () => {
return <div>Hook Exam</div>;
};
export default HookExam;
state를 호출하면 즉시 다음과 같은 오류가 발생합니다

이 오류는 useState와 같은 React Hook을 반드시 함수 컴포넌트 내부에서 호출해야 하기 때문에 발생합니다. 따라서 React Hook은 컴포넌트 외부에서 호출할 수 없습니다.
조건부로 호출된다는 것은 “React Hook이 조건문이나 반복문 내부에서 호출된다”는 의미입니다. 즉, 함수 컴포넌트 내부에서 조건문을 작성한 후 React Hook을 호출하거나, 반복문 안에서 React Hook을 호출하는 것을 말합니다. 그러나 React에서는 조건문이나 반복문 내부에서 React Hook을 호출하는 것을 절대 허용하지 않습니다.
간단히 설명하자면, React는 내부적으로 컴포넌트를 호출하여 화면에 렌더링할 때, 조건문이나 반복문 내부에서 React Hook을 호출하면 React Hook의 호출 순서가 뒤섞이는 문제가 발생합니다. 이로 인해 내부적인 오류가 발생할 가능성이 있습니다.
따라서, React Hook은 반드시 조건문이나 반복문 내부가 아닌 컴포넌트 함수의 최상위 레벨에서 호출되어야 합니다. 이렇게 해야 React가 Hook의 호출 순서를 안정적으로 관리할 수 있습니다.
HookExam 컴포넌트에 간단한 input 태그를 추가하고, 이 input에 입력된 값을 state로 관리하려면 다음과 같은 방법을 사용할 수 있습니다. 새로운 state를 생성한 후, 이벤트 핸들러를 작성합니다. 이벤트 핸들러는 매개변수로 event를 받아, 상태 업데이트 함수를 통해 입력 값을 state에 저장하는 방식으로 동작합니다.
import { useState } from "react";
const HookExam = () => {
const [input, setInput] = useState("");
const onChange = (event) => {
setInput(event.target.value);
}
return (
<div>
<input value={input} onChange={onChange} />
</div>
);
};
export default HookExam;
만약 HookExam과 같은 컴포넌트가 여러 개 필요하다면, 각 컴포넌트마다 state를 생성하고 이벤트 핸들러를 작성하는 코드를 매번 중복해서 작성해야 합니다.
이러한 중복 코드를 별도로 분리해 재사용 가능한 형태로 만들어두면, 훨씬 편리하게 활용할 수 있습니다.
import { useState } from "react";
// Custom Hook
function getInput() {
const [input, setInput] = useState("");
const onChange = (event) => {
setInput(event.target.value);
};
return [input, onChange];
}
const HookExam = () => {
const [input, onChange] = getInput();
return (
<div>
<input value={input} onChange={onChange} />
</div>
);
};
export default HookExam;
React 컴포넌트가 아닌 일반 JavaScript 함수에서 useState()와 같은 React Hook을 호출하려고 하면 오류가 발생합니다. 이는 React Hook이 반드시 React 컴포넌트 내부 또는 우리가 직접 만든 Custom Hook 내부에서만 호출될 수 있기 때문입니다.
따라서, getInput 함수를 Custom Hook으로 변환해야 합니다.
Custom Hook을 만드는 방법은 매우 간단합니다. 함수 이름 앞에 “use”라는 접두사를 붙이면 됩니다. React는 “use”로 시작하는 함수를 Custom Hook으로 인식하며, 내부에서 다른 React Hook을 호출해도 오류를 발생시키지 않습니다.
import { useState } from "react";
// Custom Hook
function useInput() {
const [input, setInput] = useState("");
const onChange = (event) => {
setInput(event.target.value);
};
return [input, onChange];
}
const HookExam = () => {
const [input, onChange] = useInput();
return (
<div>
<input value={input} onChange={onChange} />
</div>
);
};
export default HookExam;
input 처럼 컴포넌트마다 반복되어서 동작하는 로직이 있고 해당 로직이 useState와 같은 React Hook을 사용하는 로직이라면 Custom Hook을 만들어서 분리해줄 수 있으며 분리된 로직은 여러번 반복해서 사용할 수 있습니다.
import { useState } from "react";
// Custom Hook
function useInput() {
const [input, setInput] = useState("");
const onChange = (event) => {
setInput(event.target.value);
};
return [input, onChange];
}
const HookExam = () => {
const [input, onChange] = useInput();
const [input2, onChange2] = useInput();
return (
<div>
<input value={input} onChange={onChange} />
<input value={input2} onChange={onChange2} />
</div>
);
};
export default HookExam;
Custom Hook은 컴포넌트와 같은 파일에 두지 않고 src 폴더 아래 hooks라는 별도의 폴더를 만들어서 관리합니다.
[src/hooks/useInput.jsx]
import { useState } from "react";
// Custom Hook
function useInput() {
const [input, setInput] = useState("");
const onChange = (event) => {
setInput(event.target.value);
};
return [input, onChange];
}
export default useInput;
[src/components/HookExam.jsx]
import useInput from "./../hooks/useInput";
const HookExam = () => {
const [input, onChange] = useInput();
const [input2, onChange2] = useInput();
return (
<div>
<input value={input} onChange={onChange} />
<input value={input2} onChange={onChange2} />
</div>
);
};
export default HookExam;