Front-End/React

[React] Hook 상태관리와 성능최적화

HSHyeon 2024. 5. 12. 21:36
더보기

React는 선언적 UI 개발을 위한 강력한 도구를 제공하며, 그 중 핵심적인 역할을 하는 것이 바로 Hook입니다.

Hook은 상태와 사이드 이펙트를 관리하는 기능을 제공하며, 함수형 컴포넌트에서 이러한 기능을 사용할 수 있게 해줍니다. 


1. Hook의 규칙

React에서 Hook을 사용할 때는 몇 가지 규칙이 있습니다. 이 규칙들을 잘 이해하고 따르면, React 애플리케이션을 더욱 효율적으로 작성할 수 있습니다.

1.1. 최상위 레벨에서만 호출

Hook은 컴포넌트 함수의 최상위 레벨에서만 호출해야 합니다. 조건문이나 반복문, 중첩된 함수 안에서는 호출할 수 없습니다. 이 규칙을 지키는 이유는 React가 Hook의 호출 순서를 추적하기 위함입니다.

1.2. 리액트 함수 컴포넌트에서만 사용

Hook은 리액트 함수 컴포넌트 내에서만 호출 가능합니다. 클래스형 컴포넌트에서는 사용할 수 없으며, 리액트 함수형 컴포넌트에서만 사용할 수 있습니다.

1.3. 항상 같은 순서로 호출

Hook은 컴포넌트가 렌더링될 때마다 항상 같은 순서로 호출되어야 합니다. 렌더링 순서가 변경되면, React는 Hook의 상태를 추적할 수 없게 되어 오류가 발생할 수 있습니다.


2. 기본적인 Hook

2.1. useState()

useState는 함수형 컴포넌트에서 상태를 관리하기 위해 사용되는 Hook입니다.

import { useState } from 'react';

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

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={() => setCount(count + 1)}>Increment</button>
    </div>
  );
}
  • useState는 두 가지 값을 반환합니다: 상태값과 상태를 업데이트할 수 있는 함수입니다.

2.2. useEffect()

useEffect는 컴포넌트가 렌더링된 후 특정 작업을 실행할 수 있도록 해주는 Hook입니다. 예를 들어, API 호출이나 구독 설정 등을 useEffect 안에서 처리할 수 있습니다.

import { useEffect, useState } from 'react';

function DataFetcher() {
  const [data, setData] = useState(null);

  useEffect(() => {
    fetchData();
  }, []); // 빈 배열을 넣으면 마운트 시에만 실행됨

  const fetchData = async () => {
    const response = await fetch('https://api.example.com/data');
    const result = await response.json();
    setData(result);
  };

  return <div>{data ? JSON.stringify(data) : 'Loading...'}</div>;
}
  • useEffect는 첫 번째 인자로 이펙트 함수를 받고, 두 번째 인자로 의존성 배열을 받습니다.
  • 의존성 배열이 비어 있으면, useEffect는 컴포넌트가 처음 마운트될 때만 실행됩니다.
  • 의존성 배열에 값이 포함되면, 해당 값이 변경될 때마다 useEffect가 다시 실행됩니다.

2.3. useMemo() & useCallback()

useMemouseCallback은 성능 최적화를 위해 사용되는 Hook입니다. useMemo연산 결과를 기억하고, useCallback함수를 기억합니다.

useMemo()

import { useMemo } from 'react';

function ExpensiveComputation({ num }) {
  const computedValue = useMemo(() => {
    return expensiveCalculation(num);
  }, [num]);  // num이 변경될 때만 계산

  return <div>{computedValue}</div>;
}

function expensiveCalculation(num) {
  console.log('Calculating...');
  return num * 2;
}
  • useMemo는 특정 연산이 불필요하게 다시 실행되지 않도록 하기 위해 사용됩니다.
  • 의존성 배열의 값이 변경될 때만 연산이 다시 실행됩니다.

useCallback()

import { useCallback } from 'react';

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

  const increment = useCallback(() => {
    setCount(count + 1);
  }, [count]); // count가 변경될 때만 새로운 함수가 생성됨

  return <Child increment={increment} />;
}
  • useCallback함수의 재생성을 방지하여 성능을 최적화하는 데 사용됩니다.
  • 주로 자식 컴포넌트에 함수를 전달할 때, 자식이 렌더링 될 때마다 함수가 재생성되는 것을 방지합니다.

2.4. useRef()

useRefDOM 엘리먼트에 대한 참조를 저장할 수 있게 해주는 Hook입니다. 컴포넌트가 리렌더링되어도 참조된 값은 유지됩니다.

import { useRef } from 'react';

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

  const focusInput = () => {
    inputRef.current.focus();
  };

  return (
    <div>
      <input ref={inputRef} />
      <button onClick={focusInput}>Focus Input</button>
    </div>
  );
}
  • useRef렌더링 사이클에 영향을 주지 않고 값을 유지하며, DOM 참조를 통해 컴포넌트에 접근할 수 있습니다.

3. 고급 Hook 사용

3.1. useReducer()

useReduceruseState보다 복잡한 상태 관리가 필요할 때 사용됩니다. 예를 들어, 여러 개의 값이나 복잡한 상태 로직을 처리할 때 유용합니다.

import { 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:
      return state;
  }
}

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

  return (
    <div>
      <p>Count: {state.count}</p>
      <button onClick={() => dispatch({ type: 'increment' })}>Increment</button>
      <button onClick={() => dispatch({ type: 'decrement' })}>Decrement</button>
    </div>
  );
}
  • useReducer복잡한 상태 로직을 분리하여 더 관리하기 쉬운 코드로 만들 수 있습니다.

3.2. useContext()

useContext전역 상태 관리에 유용합니다. 여러 컴포넌트에서 공유하는 값을 쉽게 관리할 수 있습니다.

import React, { useContext, useState } from 'react';

// Context 생성
const ThemeContext = React.createContext('light');

function ThemedComponent() {
  const theme = useContext(ThemeContext);
  return <div>The current theme is {theme}</div>;
}

function App() {
  const [theme, setTheme] = useState('light');

  return (
    <ThemeContext.Provider value={theme}>
      <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>Toggle Theme</button>
      <ThemedComponent />
    </ThemeContext.Provider>
  );
}
  • useContext를 사용하면 전역 상태를 쉽게 공유할 수 있습니다.

3.3. useLayoutEffect()

useLayoutEffectuseEffect와 유사하지만, 렌더링 후 레이아웃을 수정하기 전에 실행되는 Hook입니다.

import { useLayoutEffect, useState, useRef } from 'react';

function LayoutEffectComponent() {
  const [width, setWidth] = useState(0);
  const divRef = useRef(null);

  useLayoutEffect(() => {
    setWidth(divRef.current.getBoundingClientRect().width);
  }, []);  // 마운트 시에만 실행됨

  return <div ref={divRef}>Width: {width}px</div>;
}
  • useLayoutEffectDOM의 크기나 위치를 측정하고 조정해야 할 때 사용됩니다.