React 실전 가이드:중급

React 입문 가이드를 통해 기본기를 다지셨다면, 이제 한 단계 더 나아갈 시간입니다. 이 중급 가이드에서는 애플리케이션의 성능을 최적화하고, 더 복잡한 데이터를 효율적으로 다루며, 코드의 품질을 높이는 실전적인 기술들을 다룹니다.

목차

  1. Hooks 심화 학습: useMemouseCallback

    • 불필요한 렌더링은 이제 그만!
    • 언제 사용해야 할까?
  2. 데이터 페칭(Data Fetching) 마스터하기

    • useEffect + Axios 패턴의 한계
    • 서버 상태 관리의 구세주: React Query
  3. 성능 최적화와 React.memo

    • React.memo로 컴포넌트 렌더링 최적화하기
    • useCallback과의 시너지
  4. 컴포넌트 생명주기(Lifecycle)와 useEffect

    • useEffect로 생명주기 흉내 내기
    • Clean-up 함수: 메모리 누수 방지
  5. UI 라이브러리 활용

    • Material-UI (MUI)로 개발 속도 높이기

1. Hooks 심화 학습: useMemouseCallback

useStateuseEffect에 익숙해졌다면, 이제 성능 최적화를 위한 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 패턴의 한계

입문 과정에서 배운 useEffectAxios를 사용한 데이터 페칭은 간단한 작업에는 유용하지만, 실무에서는 여러 한계에 부딪힙니다.

  • 로딩(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) 라이브러리인 ReduxZustand 등을 학습해보는 것을 추천합니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다