React Hooks 는 기존 Class형 컴포넌트에서만 사용 할 수 있던 상태(state)나 생명주기 함수를 함수형 컴포넌트에서도 사용 할 수 있게 해주는 기능이다.
기존 클래스형 컴포넌트에는 몇가지 단점이 있었고, 이를 해결하기 위해 react 16.8에서 Hooks가 도입되었다.
클래스 컴포넌트에서는 stae를 선언하고, this.setState()로 업데이트 한다.
컴포넌트가 커지면, 상태관리가 복잡해지고 같은 로직을 여러 컴포넌트에서 반복해서 작성해야 한다.
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}>Increase</button>
</div>
);
}
}
Hooks를 사용하면 constructor, this, setState 를 사용하지 않고 상태관리가 가능해진다.
import { useState } from "react";
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increase</button>
</div>
);
}
클래스 컴포넌트에서 componentDidMount, componentDidUpdate, componentWillUnmount 등 라이프 사이클 메서드를 사용하면 로직이 여기저기 분산되어 관리하기 어려워진다.
class Timer extends React.Component {
constructor(props) {
super(props);
this.state = { seconds: 0 };
}
componentDidMount() {
this.interval = setInterval(() => {
this.setState({ seconds: this.state.seconds + 1 });
}, 1000);
}
componentWillUnmount() {
clearInterval(this.interval);
}
render() {
return <p>Elapsed time: {this.state.seconds} sec</p>;
}
}
Hooks를 사용하면 하나의 useEffect로 모든 로직을 관리 할 수 있어 가독성과 관리가 쉬워진다.
import { useState, useEffect } from "react";
function Timer() {
const [seconds, setSeconds] = useState(0);
useEffect(() => {
const interval = setInterval(() => {
setSeconds((prev) => prev + 1);
}, 1000);
return () => clearInterval(interval); // 정리(clean-up) 로직
}, []);
return <p>Elapsed time: {seconds} sec</p>;
}
클래스 컴포넌트에서는 this를 잘못 사용하면 undifined 오류가 발생 할 수 있다.
이를 방지하기 위해 constructor 에서 this.method = this.method.bind(this) 같은 코드가 필요하다.
Hooks에서는 this 를 사용하지 않기 때문에 별도의 바인딩 과정없이 간단하게 상태를 관리 할 수 있다.
클래스 컴포넌트에서는 HOC, Render props 패턴을 사용해 로직을 재사용했지만, 코드가 복잡해지고 가독성이 떨어진다.
Hooks를 사용하면 custom Hook을 만들어 더 직관적으로 만들어 유지보수가 쉬워진다.
Wrapper Hell, Prop Drilling 등의 문제로 가독성, 유지보수 문제가 발생 할 수 있다.
기존 클래스 컴포넌트에서 this.state, this.setState로 관리하던 상태를 Hook을 통해서 함수 컴포넌트에서 간결하게 관리 할 수 있게 된다.
커스텀 훅을 만들어 여러 컴포넌트에서 재사용 가능하며, 비즈니스 로직을 컴포넌트에서 분리 할 수 있어 유지보수에 용이하다.
여러 라이프사이클 메서드를 통해 관리해야 했던 문제를 useEffect 하나에서 해결 할 수 있다.
여러 Hook을 활용하면 불필요한 렌더링을 줄여 성능을 개선하고, this 바인딩과 클래스 메서드가 없어 코드가 더 직관적이다.
if, for, while 문 에서는 호출 하면 안된다.
function MyComponent() {
const [count, setCount] = useState(0); // ✅ 항상 최상위에서 호출
}
일반 JS 함수에서 훅을 사용 할 수 없다.
useEffect의 경우 직접사용이 불가능하고, async 함수를 내부에서 선언하여 사용해야 한다.
useCallback의 경우 사용은 가능하나, 의존성 배열을 관리하기 어려워질 수 있다.
useMemo는 Promise를 반환하기 때문에, useEffect에서 상태를 업데이트 해줘야 사용 가능한 문제가 발생한다.
useState는 상태를 관리하는 훅이다.
상태와 상태를 업데이트 하는 함수를 제공하며, 해당 함수를 통해 상태를 업데이트하면 렌더링이 발생한다.
초기값을 설정 할 수 있으며, 이는 최초에만 적용된다.
컴포넌트가 다시 렌더링되어도 값이 유지된다.
상태는 객체뿐만 아니라 문자열, 숫자, null, 배열 등 여러 값을 할당 할 수 있다.
function = () => {
const [cnt, setCnt] = setState(0);
return (
<div>
<button onClick = {()=>setCnt(cnt+1)}>{cnt}</button>
</div>
)
}
## 버튼을 클릭 할 때 마다 cnt가 업데이트 되면서 렌더링이 발생한다.
컴포넌트의 라이프사이클(마운트, 업데이트, 언마운트)과 관련된 작업을 수행 할 때 사용한다.
API호출, 이벤트 리스너 등록/해제 등에 활용된다.
useEffect의 기본 형태는 useEffect(function, deps) 이며, 의존성배열(deps)에 의해 동작하는 case가 결정된다.
페이지가 렌더링 될 때 마다 동작한다.
useEffect(()=>{
console.log('work every render');
})
첫 렌더링 시에만 동작한다.
useEffect(()=>{
console.log('work only first render');
},[])
해당 값이 변할 때 마다 동작한다.
function = (propstate) => {
const [cnt, setCnt] = setState(0);
useEffect(()=>{
console.log('work state change');
},[propstate,cnt])
return (
<div>
<button onClick = {()=>setCnt(cnt+1)}>{cnt}</button>
</div>
)
}
컴포넌트 내의 변수는 렌더링이 일어날 때 마다 다시 정의된다.
useMemo를 사용하면 다시 정의되지 않고, 특정 값이 변경될때 마다 정의되도록 할 수 있다.
즉, 값의 불필요한 재계산을 방지하여 성능을 최적화한다.
const memorizedState = useMemo(()=>updateViewCnt(cnt),[cnt])
## cnt값이 변경될 때만 값이 업데이트된다.
의존성 배열이 비어있는 경우 렌더링 마다 값을 정의한다.
의존성 배열의 값은 함수에 인자로 전달되지 않으며, 함수 내에서 참조되는 모든 값은 의존성 배열에 정의되어야 한다.
불필요한 함수 생성을 방지한다.(자식 컴포넌트에 함수를 전달하는 경우)
useEffect와 유사한 표현식을 가지며, 부모컴포넌트에서 상태가 변경되고, props로 자식컴포넌트에 전달하는 경우 리렌더링이 발생한다.
자식컴포넌트의 불필요한 재정의를 방지하기 위해 이미 만들어진 함수를 재사용하며, 필요할 경우에만 재생성 하는 것으로 성능을 최적화 할 수 있다.
Dom에 직접 접근할 때 사용하거나, 값이 변경되어도 리렌더링 되지 않는 변수를 저장 할 때 사용한다.
const inputRef = useRef();
return(
<div>
<Input ref={inputRef} />
</div>
);
## inputRef.current를 통해 Input 태그 DOM에 접근 할 수 있다.
ref 값은 변경되어도 렌더링을 발생시키지 않으며, 렌더링이 발생하더라도 ref값이 업데이트 되지 않는다.
따라서 변경시 렌더링을 유발하지 않는 값을 다룰때 사용 할 수 있다.
useState를 대신하여, 복잡한 상태로직을 관리하는데에 사용된다.
reducer를 사용하면 state를 업데이트하는 로직을 컴포넌트 외부에 두고 관리 할 수 있게 된다.
const [state, dispatch] = useReducer(reducer, initialArg, init);
reducer는 이전 상태와 Action을 합쳐 새로운 상태를 만든다.
dispatch는 reducer를 실행하며, setState처럼 state를 업데이트 하는데 사용된다.
initialArg는 초기 상태를 의미한다.
init는 initialArg를 인자로 받는 함수로, 초기 상태를 계산한다. init가 지정되지 않는 경우 initialArg가 초기 상태로 설정되며, 지정된경우 init(initialArg)의 반환값을 초기상태로 설정한다.
import { useReducer } from "react";
function reducer(state, action) {
switch (action.type) {
case "increment":
return { count: state.count + 1 };
case "decrement":
return { count: state.count - 1 };
default:
return state;
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, { count: 0 });
return (
<div>
<p>Count: {state.count}</p>
<button onClick={() => dispatch({ type: "increment" })}>+</button>
<button onClick={() => dispatch({ type: "decrement" })}>-</button>
</div>
);
}
관리하는 상태가 여러개이고, 그 구조가 복잡한 경우 useState보다 useReducer를 사용하므로서 컴포넌트를 최적화 할 수 있다.
Custom Hook은 React의 내장 Hook을 조합하여 만든 재사용 가능한 Hook 이다.
여러 컴포넌트에서 반복적으로 사용되는 로직을 Hook으로 분리해 재사용 함으로서, 코드가 간결해지고 컴포넌트의 역할을 단순화 할 수 있게 된다.
함수명은 반드시 use로 시작해야 한다. (예: useFetch, useAuth, useCounter)
use로 시작하지 않으면 React에서 Hook으로 인식하지 못한다.
내부에서 React Hook을 사용할 수 있으며, 반드시 사용해야 한다.
반환값을 통해 필요한 데이터나 함수를 외부로 전달할 수 있다.
function useCustomHook() {
const [state, setState] = useState(0);
function updateState() {
setState(state + 1);
}
return [state, updateState]; // 필요한 값 반환
}
컴포넌트나 다른 Hook에서 정의하면 안된다.