React 입문 가이드를 통해 기본기를 다지셨다면, 이제 한 단계 더 나아갈 시간입니다. 이 중급 가이드에서는 애플리케이션의 성능을 최적화하고, 더 복잡한 데이터를 효율적으로 다루며, 코드의 품질을 높이는 실전적인 기술들을 다룹니다.
목차
- Hooks 심화 학습: - useMemo와- useCallback- 불필요한 렌더링은 이제 그만!
- 언제 사용해야 할까?
 
- 데이터 페칭(Data Fetching) 마스터하기 - useEffect+- Axios패턴의 한계
- 서버 상태 관리의 구세주: React Query
 
- 성능 최적화와 - React.memo- React.memo로 컴포넌트 렌더링 최적화하기
- useCallback과의 시너지
 
- 컴포넌트 생명주기(Lifecycle)와 - useEffect- useEffect로 생명주기 흉내 내기
- Clean-up 함수: 메모리 누수 방지
 
- UI 라이브러리 활용 - Material-UI(MUI)로 개발 속도 높이기
 
1. Hooks 심화 학습: useMemo와 useCallback
useState와 useEffect에 익숙해졌다면, 이제 성능 최적화를 위한 Hooks를 배울 차례입니다.
- useMemo: 값(value)을 기억(memoization)합니다. 복잡한 연산의 결과값을 저장해두고, 의존성 배열(- deps)의 값이 변경될 때만 다시 계산합니다. 이를 통해 불필요한 연산을 줄일 수 있습니다.
- useCallback: 함수(function)를 기억합니다. 컴포넌트가 리렌더링될 때마다 함수가 새로 생성되는 것을 방지합니다. 자식 컴포넌트에 props로 함수를 내려줄 때, 불필요한 리렌더링을 막는 데 특히 유용합니다.
import React, { useState, useMemo, useCallback } from 'react';
function Calculator({ a, b }) {
  // a 또는 b가 변경될 때만 expensiveCalculation 함수가 다시 실행됩니다.
  const result = useMemo(() => expensiveCalculation(a, b), [a, b]);
  // 컴포넌트가 리렌더링 되어도 이 함수는 재생성되지 않습니다.
  const handleSave = useCallback(() => {
    saveResult(result);
  }, [result]);
  return (
    <div>
      <p>결과: {result}</p>
      <ChildComponent onSave={handleSave} />
    </div>
  );
}2. 데이터 페칭 마스터하기
2.1. useEffect + Axios 패턴의 한계
입문 과정에서 배운 useEffect와 Axios를 사용한 데이터 페칭은 간단한 작업에는 유용하지만, 실무에서는 여러 한계에 부딪힙니다.
- 로딩(loading), 에러(error) 상태를 직접 useState로 관리해야 합니다.
- 데이터 캐싱(caching)이 없어, 같은 데이터를 여러 번 요청하게 됩니다.
- 코드가 길고 복잡해지기 쉽습니다.
2.2. 서버 상태 관리의 구세주: React Query
React Query는 데이터 페칭, 캐싱, 동기화, 서버 상태 업데이트 등 비동기 작업을 매우 쉽게 만들어주는 라이브러리입니다. 보일러플레이트 코드를 대폭 줄여줍니다.
- 주요 장점:
- 데이터를 가져오는 로직과 UI 로직을 분리
- 자동 캐싱 및 백그라운드 업데이트
- isLoading,- isError,- data등의 상태를 자동으로 제공
 
import { useQuery } from 'react-query';
import axios from 'axios';
// 데이터를 가져오는 함수
const fetchTodos = async () => {
  const { data } = await axios.get('https://api.example.com/todos');
  return data;
};
function TodoList() {
  // useQuery 훅 하나로 로딩, 에러, 데이터 상태를 모두 관리합니다.
  const { data: todos, isLoading, isError } = useQuery('todos', fetchTodos);
  if (isLoading) return <span>Loading...</span>;
  if (isError) return <span>Error fetching data</span>;
  return (
    <ul>
      {todos.map(todo => <li key={todo.id}>{todo.title}</li>)}
    </ul>
  );
}3. 성능 최적화와 React.memo
3.1. React.memo로 컴포넌트 렌더링 최적화하기
React.memo는 고차 컴포넌트(HOC, Higher-Order Component)로, 컴포넌트를 감싸서 props가 변경되지 않으면 리렌더링을 방지하는 역할을 합니다.
const MyComponent = (props) => {
  /* 렌더링 로직 */
};
// props가 변경될 때만 MyComponent가 리렌더링됩니다.
export default React.memo(MyComponent);3.2. useCallback과의 시너지
React.memo를 사용하더라도, props로 함수를 내려주는 경우 부모가 리렌더링될 때마다 함수가 새로 생성되어 React.memo가 무용지물이 될 수 있습니다. 이때 useCallback으로 함수를 기억시켜주면, React.memo가 제대로 동작하여 최적화 효과를 극대화할 수 있습니다.
4. 컴포넌트 생명주기(Lifecycle)와 useEffect
useEffect의 두 번째 인자인 의존성 배열(deps)을 어떻게 조작하느냐에 따라 클래스형 컴포넌트의 생명주기 메서드들을 흉내 낼 수 있습니다.
- componentDidMount:- useEffect(() => { ... }, [])(빈 배열)
- componentDidUpdate:- useEffect(() => { ... }, [dep1, dep2])(의존성 지정)
- componentWillUnmount:- useEffect(() => { return () => { ... } }, [])(Clean-up 함수 반환)
Clean-up 함수: 메모리 누수 방지
컴포넌트가 사라질 때(unmount) 실행되는 return 함수는 매우 중요합니다. setInterval, setTimeout이나 외부 라이브러리 구독 등을 설정했을 때, 컴포넌트가 사라지기 전에 이를 해제하지 않으면 메모리 누수가 발생할 수 있습니다.
useEffect(() => {
  const timerId = setInterval(() => {
    console.log('Tick');
  }, 1000);
  // 컴포넌트가 언마운트될 때 타이머를 정리합니다.
  return () => {
    clearInterval(timerId);
  };
}, []);5. UI 라이브러리 활용
모든 UI 컴포넌트를 직접 만드는 것은 비효율적일 수 있습니다. Material-UI(MUI), Ant Design, Chakra UI 같은 UI 라이브러리는 잘 디자인되고 검증된 컴포넌트들을 제공하여 개발 속도를 비약적으로 향상시켜 줍니다.
이제 여러분은 React를 좀 더 깊이 있게 이해하고, 더 견고하고 성능 좋은 애플리케이션을 만들 준비가 되었습니다. 다음 단계로는 전역 상태 관리(Global State Management) 라이브러리인 Redux나 Zustand 등을 학습해보는 것을 추천합니다.
 

답글 남기기