Programming/React
[React] React 훅(Hooks)의 정의와 특징
보간
2025. 2. 2. 20:20
훅이란?
함수형 컴포넌트에서 React의 상태(state)와 생명주기(lifecycle) 기능을 사용할 수 있게 해주는 함수
왜 훅(Hooks)이 필요했나?
컴포넌트 내부에 캡슐화 된 로직은 다른 컴포넌트와 공유가 쉽지 않았다. 결국 로직을 재사용하기 위해 React 앱에서 Render Props, HOC와 같은 복잡한 패턴을 사용해야 했다.
1. 함수의 한계: 상태 관리 불가능
함수는 순수한 로직을 재사용하기에 적합하지만, 상태를 내부적으로 관리할 수 없다.
function Counter() {
let count = 0; // 이 변수는 함수가 호출될 때마다 초기화됨
const increment = () => {
count += 1;
console.log(count);
};
return increment;
}
const counter = Counter();
counter(); // 1
counter(); // 1 (매번 초기화됨)
클로저를 사용하면 상태를 유지할 수 있지만, React 컴포넌트와 같은 환경에서는 다음 문제가 있다.
- React 컴포넌트는 상태가 변경되면 재렌더링된다. 클로저를 사용하면 상태는 유지되지만, React는 이를 감지하지 못해 재렌더링이 발생하지 않는다.
- 여러 상태를 관리하려면 클로저를 중첩해서 사용해야 하며, 코드가 복잡해진다.
- 각 컴포넌트마다 별도의 클로저를 생성해야 한다.
2. 클래스 컴포넌트의 한계: 상태 관리 가능
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
return (
<div>
<p>Count: {this.state.count}</p>
<button onClick={this.increment}>Increment</button>
</div>
);
}
}
- 코드가 복잡하고 길어진다.
- 로직을 재사용하기 어렵다.
- this 바인딩 문제*가 발생할 수 있다.
*클래스 컴포넌트에서 메서드가 이벤트 핸들러로 전달될 때 this가 undefined가 되는 문제. 일일이 메서드에 this를 바인딩해주거나 화살표 함수를 사용해야 한다.
3. 고차 컴포넌트( HOC, Higher Order Component )의 한계 : 로직 재사용
고차 컴포넌트는 컴포넌트를 감싸서 로직을 재사용한다. 하지만 코드가 복잡하고 중첩 문제가 발생할 수 있다.
function withCounter(WrappedComponent) {
return class extends React.Component {
state = { count: 0 };
increment = () => {
this.setState({ count: this.state.count + 1 }); // *
};
render() {
return (
<WrappedComponent
count={this.state.count}
increment={this.increment}
{...this.props}
/>
);
}
};
}
function Counter({ count, increment }) {
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
export default withCounter(Counter);
- 로직을 재사용할 수 있지만, 컴포넌트 계층이 복잡해진다.
- 중첩된 HOC는 디버깅을 어렵게 만든다.
*setState: React의 내장 메서드, 주로 클래스형 컴포넌트에서 상태(state)를 업데이트할 때 사용
4. 렌더 프로퍼티(Render Props)의 한계
렌더 프로퍼티는 함수를 통해 로직을 재사용한다. 하지만 코드가 복잡하고 가독성이 떨어진다.
class CounterRenderer extends React.Component {
state = { count: 0 };
increment = () => {
this.setState({ count: this.state.count + 1 });
};
render() {
// props로 전달된 render 함수를 호출하고, 현재 count와 increment 메서드를 인자로 전달
return this.props.render(this.state.count, this.increment);
}
}
function Counter() {
return (
<CounterRenderer
render={(count, increment) => (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
)}
/>
);
}
- 로직을 재사용할 수 있다.
- 코드가 복잡하고 가독성이 떨어진다.
- 중첩된 렌더 프로퍼티는 관리하기 어렵다.
5. React Hooks: 상태 관리 + 로직 재사용
Hooks는 함수형 컴포넌트에서 상태를 관리하고 로직을 재사용할 수 있게 해준다. 코드가 간결하고 이해하기 쉽다.
// React와 useState 훅을 임포트
import React, { useState } from 'react';
// useCounter라는 커스텀 훅*을 정의
function useCounter(initialValue = 0) {
//useState 훅을 사용해 상태를 관리
const [count, setCount] = useState(initialValue);
const increment = () => {
setCount(count + 1);
};
return { count, increment };
}
function Counter() {
const { count, increment } = useCounter();
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
- 상태를 관리할 수 있다.
- 로직을 재사용할 수 있다.
- 코드가 간결하고 가독성이 좋다.
- 클래스 컴포넌트의 복잡성을 피할 수 있다.
* 위 코드에서 정의한 커스텀 훅: useCounter 훅은 상태 관리와 관련된 로직을 캡슐화하여 재사용 가능하게 만듬
Hooks vs 클래스 / HOC / Render Props 비교
특징 | 클래스 | HOC | Render Props | Hooks |
상태 관리 | 가능 | 가능 | 가능 | 가능 |
로직 재사용 | 어려움 | 가능 | 가능 | 가능 |
코드 복잡도 | 높음 | 중간 | 중간 | 낮음 |
가독성 | 낮음 | 중간 | 중간 | 높음 |
중첩 문제 | 없음 | 있음 | 있음 | 없음 |
디버깅 | 어려움 | 어려움 | 어려움 | 쉬움 |
훅 사용 규칙
- 훅은 컴포넌트의 최상위 레벨에서만 호출해야 한다.
- 조건문, 반복문, 중첩 함수 내에서 훅을 호출하면 안 된다.
결론
- 클래스 컴포넌트, HOC, 렌더 프로퍼티와 비교했을 때 Hooks는 코드의 간결성, 가독성, 재사용성 측면에서 우수하다.
- Hooks를 사용하면 함수의 한계인 상태 관리 문제를 해결하면서도, 함수의 장점인 코드 재사용성을 극대화할 수 있다.