본문 바로가기

Language/React

[개념] React Hook

React Hook이란?
  • 함수형 컴포넌트에서 상태(state)와 생명주기(lifecycle) 기능을 사용할 수 있도록 도입된 기능
  • 이전에는 클래스형 컴포넌트에서만 상태와 생명주기 관리를 할 수 있었지만, Hook이 등장하면서 함수형 컴포넌트로도 복잡한 로직을 간단하게 구현이 가능해짐

React Hook의 특징
  • 함수형 컴포넌트를 강화 : 상태와 생명주기 관리를 위해 클래스형 컴포넌트를 사용할 필요가 없어짐
  • 재사용 가능한 로직 : Custom Hook을 만들어 중복 코드를 줄이고 로직을 재사용 가능
  • 가독성 향상 : 로직을 분리하고 구조화할 수 있어 코드가 더 깔끔해짐
  • React 16.8 이상에서 사용 가능 :Hook은 React 16.8 버전에서 도입 됨

함수형 컴포넌트 ? 클래스형 컴포넌트 ?
함수형 컴포넌트

 

  • JavaScript 함수처럼 작성
  • React Hook(useState, useEffect 등)을 사용하여 상태와 생명주기 관리
  • 구조가 간단하고 코드가 짧음
  • 예시
    • useState Hook을 사용하여 count 상태를 관리
    • 버튼 클릭 시 setCount를 호출해 상태를 변경
    • 단순하고 읽기 쉬운 코드 구조
import React, { useState } from 'react';

function Counter() {
  // useState로 상태 관리
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
      <button onClick={() => setCount(count - 1)}>감소</button>
    </div>
  );
}

export default Counter;

 

클래스형 컴포넌트
  • ES6 클래스 문법을 사용하여 작성
  • 상태를 관리하려면 this.state를 사용
  • 상태를 업데이트하려면 this.setState를 호출
  • 생명주기 메서드(componentDidMount, componentDidUpdate 등)를 사용하여 생명주기를 관리
  • 예시
    • state 객체를 사용하여 상태를 관리
    • this.setState 메서드상태를 변경
    • 클래스 내부의 메서드를 정의하여 버튼 클릭 시 호출
import React, { Component } from 'react';

class Counter extends Component {
  // 클래스에서 상태 초기화
  state = {
    count: 0,
  };

  // 메서드를 사용해 상태 업데이트
  increment = () => {
    this.setState({ count: this.state.count + 1 });
  };

  decrement = () => {
    this.setState({ count: this.state.count - 1 });
  };

  render() {
    return (
      <div>
        <p>현재 카운트: {this.state.count}</p>
        <button onClick={this.increment}>증가</button>
        <button onClick={this.decrement}>감소</button>
      </div>
    );
  }
}

export default Counter;

 

차이점
항목 함수형 컴포넌트 클래스형 컴포넌트
구조 함수형으로 작성 클래스형으로 작성
상태 관리 useState Hook 사용 this.state와 this.setState 사용
생명주기 관리 useEffect Hook 사용 생명주기 메서드 사용
코드 간결성 간결하고 짧음 상대적으로 복잡하고 길어짐
React 버전 React 16.8 이상에서 지원 모든 React 버전에서 지원

React Hook 규칙

 

최상위에서만 호출
  • 반복문, 조건문, 중첩된 함수 내부에서 호출하면 안 됨
    → 실행 여부가 달라질 수 있어 React가 Hook 호출 순서를 잘못 추적
  • React는 Hook이 호출된 순서를 기준으로 상태를 관리하므로, 순서가 바뀌면 버그가 발생
  • 잘못된 사용
function MyComponent() {
  if (true) { // 조건문 안에서 Hook 호출
    const [state, setState] = useState(0); // 오류 발생 가능
  }

  return <div>안녕하세요!</div>;
}
  • 올바른 사용
function MyComponent() {
  // 최상위에서만 Hook 호출
  const [state, setState] = useState(0);

  return <div>안녕하세요!</div>;
}
React 함수에서만 호출
  • 함수형 컴포넌트나 Custom Hook에서만 사용 가능
    → React의 상태 관리와 생명주기 로직은 함수형 컴포넌트와 Hook에 맞게 설계
  • 일반 JavaScript 함수나 클래스형 컴포넌트에서는 호출하면 안 됨
    → 일반 함수에서 호출하면 React의 상태 관리 메커니즘이 동작하지 않음
  • 잘못된 사용
function regularFunction() {
  const [state, setState] = useState(0); // 오류 발생
}
  • 올바른 사용 (함수형 컴포넌트)
function MyComponent() {
  const [state, setState] = useState(0); // 정상 동작
  return <div>{state}</div>;
}
  • 올바른 사용 (Custom Hook)
function useCustomHook() {
  const [value, setValue] = useState(0); // Hook 호출 가능
  return [value, setValue];
}

function MyComponent() {
  const [customState, setCustomState] = useCustomHook();
  return <div>{customState}</div>;
}

 


React Hook의 장단점
  • 장점
    • 함수형 컴포넌트에서 상태와 생명주기 관리가 가능
    • Custom Hook을 통해 로직을 재사용
    • 클래스형 컴포넌트보다 코드가 간결하고 이해하기 쉬움
    • Props Drilling 문제를 해결 (useContext 활용)
  • 단점
    • 복잡한 Hook 조합은 가독성을 떨어뜨릴 수 있음
    • 초보자에게 규칙을 준수하며 사용하기가 다소 어렵게 느껴질 수 있음
    • 상태가 많아질수록 코드가 비대해질 가능성 있음

React에서 제공하는 주요 Hook
useState : 상태 관리
  • 함수형 컴포넌트에서 상태를 관리하는 데 사용
const [state, setState] = useState(initialValue);
  • 특징
    • state : 현재 상태 값을 나타냄
    • setState : 상태를 업데이트하는 함수
    • 상태 변경 시 컴포넌트가 다시 렌더링
import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0); // 초기값: 0

  return (
    <div>
      <p>현재 카운트: {count}</p>
      <button onClick={() => setCount(count + 1)}>증가</button>
    </div>
  );
}

useEffect : 생명주기 관리
  • 컴포넌트렌더링 or 업데이트 or 언마운트될 때 실행할 작업을 지정
useEffect(() => {
  // 실행할 작업
  return () => {
    // 정리 작업 (옵션)
  };
}, [dependencyArray]); // 의존성 배열
  • 특징
    • 의존성 배열([])이 없는 경우 : 매 렌더링마다 실행
    • 빈 배열([])인 경우 : 처음 한 번만 실행 (componentDidMount와 비슷)
    • 배열에 값이 있는 경우 : 값이 변경될 때만 실행
import React, { useState, useEffect } from 'react';

function Timer() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setCount((prev) => prev + 1), 1000);
    return () => clearInterval(interval); // 컴포넌트 언마운트 시 정리
  }, []);

  return <div>
    <p>타이머: {Math.floor(count/60)}분 {count%60}초</p>
    <p>타이머: {count}초</p>
  </div>;
  ;
}

export default Timer;

useContext : 전역 상태 관리
  • Context API를 활용컴포넌트 트리에서 전역 데이터를 쉽게 공유
const value = useContext(MyContext);
  • 특징
    • Props Drilling(여러 컴포넌트 계층을 통해 props 전달) 문제를 해결
    • Context.Provider에서 제공하는 값을 구독
import React, { useContext, createContext } from 'react';

const ThemeContext = createContext('light');

function ThemedComponent() {
  const theme = useContext(ThemeContext);
  return <p>현재 테마: {theme}</p>;
}

function App() {
  return (
    <ThemeContext.Provider value="dark">
      <ThemedComponent />
    </ThemeContext.Provider>
  );
}

export default App;

useReducer : 복잡한 상태 관리
  • Redux와 유사한 방식으로 상태를 업데이트하는 Hook
  • 복잡한 상태 로직이 필요한 경우 유용
const [state, dispatch] = useReducer(reducer, initialState);
  • 특징
    • reducer : 상태 업데이트 로직을 정의하는 함수
      (state, action) => newState
    • dispatch : 액션을 보내는 함수
    • state, dispatch, reducer예약어가 아님
      → reducer와 dispatch는 useReducer의 컨벤션으로 자주 사용 되는 이름
import React, { useReducer } from 'react';

const initialState = { count: 0 };

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error();
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, initialState);

  return (
    <div>
      <p>카운트: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>증가</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>감소</button>
    </div>
  );
}

export default Counter;

  • state, reducer, dispatch 이름 변경
    • state => currentState
    • reducer 함수 => customReducer 함수
    • dispatch => sendAction
import React, { useReducer } from 'react';

const initialState = { count: 0 };

// 리듀서 이름 변경 (자유롭게 설정 가능)
function customReducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    case 'decrement':
      return { count: state.count - 1 };
    default:
      throw new Error('Unknown action type');
  }
}

function Counter() {
  const [currentState, sendAction] = useReducer(customReducer, initialState);

  return (
    <div>
      <p>Count: {currentState.count}</p>
      <button onClick={() => sendAction({ type: 'increment' })}>증가</button>
      <button onClick={() => sendAction({ type: 'decrement' })}>감소</button>
    </div>
  );
}

export default Counter;

요약
  • React Hook상태 관리와 생명주기 관리를 함수형 컴포넌트에서 가능하게 해줌
  • 주요 Hook
    • useState : 상태 관리
    • useEffect : 생명주기 관리
    • useContext : 전역 데이터 공유
    • useReducer : 복잡한 상태 로직 관리
  • 규칙을 반드시 준수해야 React가 올바르게 동작
  • 장점과 단점을 이해하고 상황에 맞게 사용하면 생산성 높은 개발이 가능