본문 바로가기
JavaScript/React

[React] React Hooks이란 ?

by 승븐지 2024. 11. 27.
반응형

 

반응형
 

1. 훅(Hooks)란 무엇일까?

React에서 훅(Hooks)은 함수형 컴포넌트에서 React의 기능(상태 관리, 생명 주기 관리 등)을 사용할 수 있게 해주는 도구입니다.
원래는 클래스형 컴포넌트에서만 상태를 다루거나 생명 주기를 관리할 수 있었는데, 훅 덕분에 함수형 컴포넌트에서도 이런 기능을 사용할 수 있게 되었습니다.

훅의 장점

  • 더 간결한 코드 작성 가능.
  • 재사용성과 가독성 향상.
  • 클래스 없이 상태나 생명 주기 같은 React 기능을 사용 가능.

 


2. 자주 사용하는 기본 훅들

React에서 제공하는 몇 가지 기본 훅이 있습니다. 

2.1. useState

컴포넌트에서 상태를 관리하기 위한 훅입니다.

  • 상태(state)는 UI가 기억해야 할 값이라고 생각하면 됍니다.
    예를 들어, 버튼을 클릭했을 때 숫자를 증가시키고 싶다면 숫자 상태를 저장해야겠죠?
import React, { useState } from "react";

function Counter() {
  // useState(초기값): 상태와 상태를 업데이트하는 함수를 반환합니다.
  const [count, setCount] = useState(0);

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

export default Counter;

 

어떻게 동작하냐면:

  1. useState(0)은 초기값 0을 가진 상태 count를 만듭니다.
  2. setCount를 호출하면 상태가 변경되고, React가 화면을 다시 렌더링합니다.

 

2.2. useEffect

컴포넌트가 렌더링되거나 상태가 변할 때 실행할 작업을 정의하는 훅입니다.

  • 예를 들어, 컴포넌트가 처음 화면에 나타났을 때 데이터를 가져오거나, 컴포넌트가 사라질 때 정리(clean-up) 작업을 하고 싶을 때 사용합니다.
import React, { useState, useEffect } from "react";

function Timer() {
  const [seconds, setSeconds] = useState(0);

  // useEffect: 컴포넌트가 렌더링될 때마다 실행
  useEffect(() => {
    const interval = setInterval(() => {
      setSeconds((prev) => prev + 1);
    }, 1000);

    // 정리 작업(clean-up): 컴포넌트가 사라질 때 실행
    return () => clearInterval(interval);
  }, []); // []는 의존성 배열로, 특정 값이 변경될 때만 실행되게 함.

  return <p>타이머: {seconds}초</p>;
}

export default Timer;

 

 

어떻게 동작하냐면:

  1. 컴포넌트가 처음 렌더링될 때 useEffect 내부 코드가 실행됍니다.
  2. 컴포넌트가 사라질 때(unmount), 정리 작업이 실행됍니다(return 함수).

 

2.3. useRef 

DOM 요소나 값의 참조를 저장하기 위한 훅입니다.

  • 상태와 달리 값이 바뀌어도 리렌더링되지 않아. 예를 들어, 입력 필드에 포커스를 주거나 특정 값을 저장해야 할 때 유용합니다.
import React, { useRef } from "react";

function InputFocus() {
  const inputRef = useRef(null);

  const handleFocus = () => {
    inputRef.current.focus(); // inputRef가 연결된 input에 포커스
  };

  return (
    <div>
      <input ref={inputRef} type="text" />
      <button onClick={handleFocus}>포커스 주기</button>
    </div>
  );
}

export default InputFocus;

 

어떻게 동작하냐면:

  1. useRef는 inputRef라는 참조 객체를 만듭니다.
  2. ref 속성을 통해 input과 연결돼, 이후 inputRef.current로 실제 DOM 요소에 접근할 수 있습니다.

 

2.4 useContext: 전역 데이터 공유

 

useContext는 컴포넌트 트리에서 데이터를 자식 컴포넌트로 쉽게 전달할 수 있도록 해줍니다.
props를 계속 전달하지 않아도 됍니다!

 

import React, { createContext, useContext } from "react";

// 1. Context 생성
const ThemeContext = createContext();

function App() {
  return (
    // 2. Context Provider로 데이터 제공
    <ThemeContext.Provider value="dark">
      <Toolbar />
    </ThemeContext.Provider>
  );
}

function Toolbar() {
  return (
    <div>
      <ThemedButton />
    </div>
  );
}

function ThemedButton() {
  // 3. Context 데이터 사용
  const theme = useContext(ThemeContext);
  return <button style={{ background: theme === "dark" ? "#333" : "#ccc", color: "#fff" }}>Theme: {theme}</button>;
}

export default App;

 

  • ThemeContext.Provider를 통해 value="dark"라는 데이터를 제공.
  • useContext(ThemeContext)를 사용해 현재 테마를 쉽게 가져올 수 있습니다.

 

2.5 useReducer: 복잡한 상태 로직 처리

useReducer는 useState보다 복잡한 상태 관리 로직을 처리할 때 유용합니다.
예를 들어, 여러 상태를 한 곳에서 관리하거나 상태 변경 로직이 복잡할 때 좋습니다.

 

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 };
    case "reset":
      return { count: 0 };
    default:
      throw new Error();
  }
}

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

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: "increment" })}>+</button>
      <button onClick={() => dispatch({ type: "decrement" })}>-</button>
      <button onClick={() => dispatch({ type: "reset" })}>Reset</button>
    </div>
  );
}

export default Counter;

 

dispatch({ type: "increment" })처럼 액션을 보내면 reducer 함수가 실행되고 상태가 업데이트됍니다.

 

 

2.6 useMemo: 계산 결과 캐싱

useMemo는 성능 최적화를 위해 특정 값이 변하지 않으면 이전에 계산한 결과를 재사용합니다.
비싼 계산(시간이 오래 걸리는 작업)을 반복하지 않도록 도와줍니다.

 

import React, { useState, useMemo } from "react";

function ExpensiveCalculation(num) {
  console.log("Expensive calculation...");
  return num * 2;
}

function App() {
  const [count, setCount] = useState(0);
  const [input, setInput] = useState("");

  // useMemo로 계산 결과 캐싱
  const doubled = useMemo(() => ExpensiveCalculation(count), [count]);

  return (
    <div>
      <p>Doubled Count: {doubled}</p>
      <button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
      <input value={input} onChange={(e) => setInput(e.target.value)} />
    </div>
  );
}

export default App;

 

 

count가 바뀌지 않으면 ExpensiveCalculation이 다시 실행되지 않아, 성능이 향상됍니다.

 

 

2.7 useCallback: 함수 재생성 방지

useCallback은 특정 의존성이 변경될 때만 함수를 새로 생성합니다.
컴포넌트가 자주 리렌더링되는 경우 성능 최적화에 유용합니다.

 

import React, { useState, useCallback } from "react";

function Child({ onClick }) {
  console.log("Child rendered");
  return <button onClick={onClick}>Click me</button>;
}

function App() {
  const [count, setCount] = useState(0);
  const [text, setText] = useState("");

  // useCallback으로 함수 재생성을 방지
  const handleClick = useCallback(() => {
    setCount((prev) => prev + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <Child onClick={handleClick} />
      <input value={text} onChange={(e) => setText(e.target.value)} />
    </div>
  );
}

export default App;

 

 

 

  • useCallback을 사용하면 Child 컴포넌트가 불필요하게 리렌더링되지 않습니다.
  • 의존성 배열([])에 따라 함수가 재생성되지 않고 캐싱됍니다.

3. 훅의 규칙

React 훅은 몇 가지 규칙을 따라야 합니다 

  1. 훅은 함수 컴포넌트 또는 커스텀 훅 내부에서만 사용해야 합니다.
    • 예: useState를 컴포넌트 함수 밖에서 호출하면 안 됍니다.
  2. 훅은 항상 동일한 순서로 호출해야 합니다.
    • 조건문이나 반복문 안에서 호출하면 안 됍니다.

 

4. 훅을 사용하는 이유

  1. 코드 재사용성이 좋아집니다.
    • 클래스 컴포넌트에서는 로직을 재사용하기 어려웠지만, 훅은 커스텀 훅을 만들어 쉽게 로직을 재사용할 수 있습니다.
  2. 더 간결하고 읽기 쉬운 코드.
    • 클래스 컴포넌트의 복잡한 생명 주기 메서드 대신, useEffect 하나로 간단히 처리 가능합니다.

 

5. 훅 주요 기능 및 간단 예시

useState 컴포넌트에서 상태를 관리 (ex. 숫자, 문자열, 배열 등).
useEffect 컴포넌트의 생명주기(렌더링, 업데이트, 언마운트)에 따라 작업 수행.
useRef DOM 요소나 값의 참조를 저장, 값이 변경되어도 리렌더링되지 않음.
useContext 컴포넌트 트리에서 데이터를 전역적으로 쉽게 공유.
useReducer 복잡한 상태 로직을 처리하고, 여러 상태를 한 곳에서 관리.
useMemo 계산 결과를 캐싱해 불필요한 재계산 방지 (성능 최적화).
useCallback 함수의 재생성을 방지해 컴포넌트의 불필요한 리렌더링 방지.

 

 

간단하게 생각해보자면은 

  • useState: 클릭 카운트 상태 관리
  • useEffect: API 호출 및 컴포넌트가 사라질 때 정리 작업
  • useRef: 입력 필드 포커스
  • useContext: 다크 모드 상태 공유
  • useReducer: 카운터 증감 로직 통합
  • useMemo: 계산된 값 캐싱 (ex. 배열 정렬)
  • useCallback: 클릭 이벤트 핸들러 캐싱

 

반응형