React(가상 DOM)에서 GSAP(실제 DOM 조작)을 사용할 때는 컴포넌트가 언마운트될 때 진행 중인 애니메이션과 ScrollTrigger를 정리(Cleanup)해 주어야 메모리 누수와 버그를 막을 수 있습니다.
기존에는 useEffect와 gsap.context()를 묶어서 복잡하게 처리했지만, GSAP 공식 React 훅인 @gsap/react (useGSAP)를 사용하면 내부적으로 스코프 격리와 클린업을 완벽하게 자동 처리해 줍니다.
💡 핵심 패턴
useGSAP(() => { ... }, { scope: containerRef, dependencies: [state] }); scope 지정: 컴포넌트 외부의 동명이인 요소('.box' 등)를 건드리지 않도록 안전하게 격리합니다. 자동 Cleanup: 컴포넌트가 사라지거나 재랜더링될 때 애니메이션을 깔끔하게 폐기(revert)합니다.
import React, { useRef, useState } from 'react';
import gsap from 'gsap';
import { useGSAP } from '@gsap/react'; // GSAP 공식 React 훅
// 플러그인 등록 (Next.js/React 공통)
gsap.registerPlugin(useGSAP);
export default function App() {
const containerRef = useRef(null);
const [clickCount, setClickCount] = useState(0);
// 💡 useEffect 대신 useGSAP 사용!
useGSAP(() => {
// 1. scope 옵션 덕분에 containerRef 내부의 '.box'만 선택됩니다.
// 2. 컴포넌트 언마운트 시 자동 Cleanup (revert) 됩니다.
gsap.from('.box', {
y: 50,
opacity: 0,
stagger: 0.1,
duration: 1,
ease: 'back.out(1.5)'
});
// 버튼을 클릭할 때마다 텍스트 애니메이션 (dependencies 배열 활용)
if (clickCount > 0) {
gsap.fromTo('.counter',
{ scale: 1.5, color: '#f59e0b' },
{ scale: 1, color: '#94a3b8', duration: 0.5, clearProps: 'all' }
);
}
}, {
scope: containerRef,
dependencies: [clickCount] // clickCount가 변할 때마다 재실행 및 자동 정리
});
return (
<div className="container">
<div ref={containerRef} className="card">
<h2 className="title">useGSAP in React</h2>
<div className="box-container">
<div className="box"></div>
<div className="box"></div>
<div className="box"></div>
</div>
<div className="controls">
<button
className="action-btn"
onClick={() => setClickCount(c => c + 1)}
>
클릭하여 의존성 재실행 🚀
</button>
<p className="counter">클릭 횟수: {clickCount}</p>
</div>
</div>
</div>
);
}