TypeScript

[React] memo를 사용한 컴포넌트 최적화

코샵 2024. 12. 25. 18:45
반응형

소개

React의 memo 함수는 컴포넌트의 불필요한 리렌더링을 방지하여 성능을 최적화하는 데 사용됩니다. 이번 글에서는 memo의 올바른 사용법과 실제 사례를 살펴보겠습니다.

기본 사용법

import { memo } from 'react';

interface UserProfileProps {
  name: string;
  email: string;
}

const UserProfile = memo(function UserProfile({ name, email }: UserProfileProps) {
  return (
    <div className="user-profile">
      <h2>{name}</h2>
      <p>{email}</p>
    </div>
  );
});

export default UserProfile;

memo가 필요한 경우와 필요하지 않은 경우

memo가 유용한 경우

// 1. 큰 리스트의 항목 컴포넌트
const TodoItem = memo(function TodoItem({ todo, onToggle }: TodoItemProps) {
  return (
    <li>
      <input
        type="checkbox"
        checked={todo.completed}
        onChange={() => onToggle(todo.id)}
      />
      {todo.text}
    </li>
  );
});

// 2. 자주 변경되지 않는 데이터를 표시하는 컴포넌트
const StaticHeader = memo(function StaticHeader({ title }: HeaderProps) {
  return (
    <header>
      <h1>{title}</h1>
    </header>
  );
});

memo가 불필요한 경우

// 1. 자주 변경되는 데이터를 가진 컴포넌트
const Timer = () => {
  const [time, setTime] = useState(0);

  useEffect(() => {
    const interval = setInterval(() => setTime(t => t + 1), 1000);
    return () => clearInterval(interval);
  }, []);

  return <div>{time} seconds</div>;
};

// 2. 단순한 컴포넌트
const Button = ({ onClick, children }) => (
  <button onClick={onClick}>
    {children}
  </button>
);

커스텀 비교 함수 사용

const ExpensiveComponent = memo(
  function ExpensiveComponent({ data, onUpdate }: Props) {
    return (
      <div>
        {/* 복잡한 렌더링 로직 */}
      </div>
    );
  },
  (prevProps, nextProps) => {
    // 깊은 비교 수행
    return JSON.stringify(prevProps.data) === JSON.stringify(nextProps.data);
  }
);

성능 최적화 예제

리스트 최적화

interface ListProps {
  items: Item[];
  onItemClick: (id: string) => void;
}

const OptimizedList = memo(function OptimizedList({ 
  items, 
  onItemClick 
}: ListProps) {
  return (
    <ul>
      {items.map(item => (
        <ListItem
          key={item.id}
          item={item}
          onClick={onItemClick}
        />
      ))}
    </ul>
  );
});

const ListItem = memo(function ListItem({ 
  item, 
  onClick 
}: ListItemProps) {
  return (
    <li onClick={() => onClick(item.id)}>
      {item.name}
    </li>
  );
});

폼 컴포넌트 최적화

const FormField = memo(function FormField({
  label,
  value,
  onChange,
  error
}: FormFieldProps) {
  return (
    <div className="form-field">
      <label>{label}</label>
      <input value={value} onChange={onChange} />
      {error && <span className="error">{error}</span>}
    </div>
  );
});

// 사용 예시
const Form = () => {
  const [formData, setFormData] = useState({ name: '', email: '' });

  const handleNameChange = useCallback((e: React.ChangeEvent<HTMLInputElement>) => {
    setFormData(prev => ({ ...prev, name: e.target.value }));
  }, []);

  return (
    <form>
      <FormField
        label="Name"
        value={formData.name}
        onChange={handleNameChange}
      />
      {/* 다른 필드들 */}
    </form>
  );
};

memo와 다른 Hooks의 조합

useMemo와 함께 사용

const ComplexComponent = memo(function ComplexComponent({ 
  data,
  onProcess 
}: ComplexProps) {
  const processedData = useMemo(() => {
    return expensiveOperation(data);
  }, [data]);

  return (
    <div>
      {processedData.map(item => (
        <div key={item.id}>{item.value}</div>
      ))}
    </div>
  );
});

useCallback과 함께 사용

const ParentComponent = () => {
  const [items, setItems] = useState<string[]>([]);

  const handleAdd = useCallback((newItem: string) => {
    setItems(prev => [...prev, newItem]);
  }, []);

  return (
    <MemoizedChildComponent
      items={items}
      onAdd={handleAdd}
    />
  );
};

const MemoizedChildComponent = memo(function ChildComponent({
  items,
  onAdd
}: ChildProps) {
  return (
    <div>
      <button onClick={() => onAdd('new item')}>Add Item</button>
      <ul>
        {items.map(item => (
          <li key={item}>{item}</li>
        ))}
      </ul>
    </div>
  );
});

성능 측정

const withPerformanceLogging = <P extends object>(
  WrappedComponent: React.ComponentType<P>,
  componentName: string
) => {
  return memo(function LoggedComponent(props: P) {
    console.time(`${componentName} render`);
    const result = <WrappedComponent {...props} />;
    console.timeEnd(`${componentName} render`);
    return result;
  });
};

const OptimizedComponent = withPerformanceLogging(MyComponent, 'MyComponent');

주의사항

  1. 과도한 사용 피하기
  2. 올바른 의존성 관리
  3. 깊은 비교의 성능 영향 고려
  4. 컴포넌트의 책임 범위 명확히 하기

마치며

memo는 React 애플리케이션의 성능을 최적화하는 강력한 도구입니다. 하지만 모든 컴포넌트에 무분별하게 적용하는 것은 오히려 성능을 저하시킬 수 있으므로, 실제 성능 측정을 통해 필요한 곳에만 적용하는 것이 중요합니다.