useEvent
React 19에서 공식 도입된 useEvnet는 기존 useEffect, useCallback이 해결하지 못하던 stale closure 문제를 해결하고, 변하지 않는(stable) 이벤트 핸들러를 만들기 위한 새로운 훅입니다.
1️⃣ useEvent란?
"항상 최신 state와 props를 읽으면서도, 함수 참조(stable reference)는 절대 변하지 않는 이벤트 핸들러를 만드는 훅"
React 외부에서 실행되는 콜백(브라우저 이벤트, 타이머, 서드파티 라이브러리 등)에서 stable closure 문제가 생기지 않도록 설계되었습니다.
2️⃣ 기존 문제: stable closure
useEffect(() => {
window.addEventListener("scroll", () => {
console.log(count); // 오래된 count를 계속 사용
});
}, []);
count가 바뀌어도 이벤트 핸들러는 처음 렌더링 때의 count만 기억합니다.
→ 이것이 stale closure(오래된 클로저) 문제.
해결하려고 count를 dependency에 넣으면?
- 이벤트 리스너가 매번 등록/해제돼서 비효율적
3️⃣ useEvent로 해결
const handleScroll = useEvent(() => {
console.log(count); // 항상 최신 count
});
useEffect(() => {
window.addEventListener("scroll", handleScroll);
return () => window.removeEventListener("scroll", handleScroll);
}, []); // deps 필요 없음
✅ 효과
handleScroll참조는 절대 변하지 않음- 내부에서는 항상 최시 state/count 읽음
- 이벤트 리스너를 한 번만 등록해도 괜찮음
4️⃣ 언제 useEvent를 사용해야 하나?
🔹 React 외부에서 실행되는 이벤트
window.addEventListener('scroll')resize,mousemove등 글로벌 이벤트
🔹 setInterval, setTimeout 안에서 최신 값이 필요할 때
const tick = useEvent(() => {
console.log(count); // 최신 count 보장
});
useEffect(() => {
const id = setInterval(tick, 1000);
return () => clearInterval(id);
}, []);
🔹 서드파티 라이브러리 콜백
- 지도(onMove)
- 차트(onHover)
- 소켓(message)
🔹 최신 state가 필요한데, 이벤트 핸들러 참조가 바뀌면 안 되는 경우
- 예: expensive 이벤트 리스너 재등록 방지
5️⃣ useCallback vs useEvent
| useCallback | useEvent |
|---|---|
| 함수 참조 안정성 | ⭕ (deps 변화 없을 때만) |
| 최신 state 보장 | ❌ stale closure 가능 |
| deps 필요 | ⭕ 필요함 |
| 외부 이벤트에 적합 | ⚠️ 제한적 |