움직이는 UI, 살아있는 컴포넌트 (react-spring)

742025년 10월 28일3

데이터 패칭의 세계가 훅에 의해 재편되고 있을 무렵, 또 다른 영역에서도 조용한 혁명이 일어나고 있었다. 바로 ‘애니메이션’의 세계였다.

과거, 리액트에서 부드러운 애니메이션을 구현하는 것은 상당한 고통을 수반하는 일이었다. 개발자들은 CSS 트랜지션(transition)의 한계에 부딪히거나, 복잡한 생명주기 메서드 안에서 requestAnimationFrame을 직접 관리하며 수많은 상태와 씨름해야 했다.

컴포넌트가 마운트될 때 페이드인(fade-in) 효과를 주고, 언마운트될 때 페이드아웃(fade-out) 효과를 주는 간단한 작업조차, react-transition-group 같은 라이브러리의 복잡한 API와 싸워야만 했다.

이때, 폴 헨셸(Paul Henschel)이라는 개발자가 만든 react-spring이라는 라이브러리가 훅 기반의 새로운 API를 선보이며 판도를 바꾸기 시작했다.

react-spring의 철학은 독특했다. 시간(duration)과 곡선(easing curve)에 기반한 전통적인 애니메이션 방식에서 벗어나, 물리 법칙(스프링의 탄성)에 기반한 애니메이션을 추구했다. 이 접근법은 훨씬 더 자연스럽고 생동감 있는 움직임을 만들어냈다.

그리고 훅은 이 철학을 구현하는 완벽한 도구였다.

한 개발자가 모달 창이 나타날 때 부드럽게 떠오르는 효과를 구현하며 괴로워하고 있었다. 클래스 컴포넌트로 이 로직을 짜는 것은 상태 관리와 생명주기, 그리고 스타일링이 뒤섞인 복잡한 작업이었다.

그는 우연히 react-spring의 새로운 useSpring 훅을 발견했다.
그가 작성해야 할 코드는 충격적일 정도로 간결했다.

import { useSpring, animated } from 'react-spring';

function MyModal({ isOpen }) {
  // useSpring 훅을 사용하여 애니메이션 스타일을 정의한다.
  const animationProps = useSpring({
    // isOpen이 true이면 opacity는 1, false이면 0이 된다.
    opacity: isOpen ? 1 : 0,
    // 애니메이션의 시작 상태를 지정한다.
    from: { opacity: 0 },
  });

  if (!isOpen) return null;

  return (
    // animated.div 컴포넌트에 계산된 스타일을 적용한다.
    <animated.div style={animationProps} className="modal">
      <h1>Hello, World!</h1>
    </animated.div>
  );
}

이것이 전부였다.
useSpring 훅은 isOpen이라는 boolean 값의 변화를 감지하여, opacity 값을 0에서 1로, 혹은 1에서 0으로 부드럽게 보간(interpolate)하는 모든 복잡한 계산을 내부적으로 처리했다.

개발자는 더 이상 setTimeout이나 requestAnimationFrame을 직접 다룰 필요가 없었다.
그저 ‘시작 상태’와 ‘끝 상태’만 선언적으로 정의하면, react-spring이 나머지를 모두 알아서 처리해주었다.

useSpring, useTrail, useChain
react-spring이 제공하는 다양한 훅들은, 마치 살아있는 생명체처럼 UI 요소들을 움직이게 만들었다. 훅은 시간에 따라 변하는 상태(애니메이션의 진행 과정)를 컴포넌트의 렌더링과 자연스럽게 융합시키는 완벽한 매개체였다.

이것은 훅이 단순히 데이터 로직뿐만 아니라, 사용자와의 상호작용에서 가장 중요한 시각적 경험까지도 얼마나 우아하게 다룰 수 있는지를 보여주는 사례였다.

리액트 생태계는 점점 더 풍성해지고 있었다.
개발자들은 이제 데이터 통신, 라우팅, 그리고 애니메이션에 이르기까지, 복잡한 문제들을 해결해주는 강력한 커스텀 훅들을 자신의 무기고에 채워 넣을 수 있게 되었다.

그리고 그들의 다음 목표는, 웹 개발의 영원한 숙제이자 가장 큰 골칫거리 중 하나인 ‘폼(Form) 관리’를 정복하는 것이었다.