개요
React 공식 문서를 살펴보다가, 19버전에서 useEffectEvent 훅이 실험 단계(Experimental)에서 정식 훅으로 변경된 것을 보고 더 자세히 알아보기 위해 공부해보았다.
https://ko.react.dev/reference/react/useEffectEvent
useEffectEvent – React
The library for web and native user interfaces
ko.react.dev
왜 만들어진 훅인가?
이벤트 핸들러를 useEffect 훅을 사용해서 등록한다면 다음과 같이 작성할 수 있다.
function MyComponent() {
const [count, setCount] = useState(0);
useEffect(() => {
function onClick() {
console.log(count); // 항상 0만 출력됨
}
window.addEventListener('click', onClick);
return () => window.removeEventListener('click', onClick);
}, []); // 의존성이 없기 때문
return <button onClick={() => setCount((c) => c + 1)}>증가</button>;
}
위 코드는 문제가 있다.
onClick 이벤트를 수행하면 count를 출력하지만 클릭할 때 마다 1씩 증가하는게 아닌 0만 출력된다.
의존성 배열에 아무것도 들어있지 않기 때문이다.
onClick 함수가 기억하는 count의 값은 항상 0인 stale closure 문제가 발생한다.
이는 보통 아래와 같이 해결할 수 있다.
useEffect(() => {
function onClick() {
console.log(count);
}
window.addEventListener('click', onClick);
return () => window.removeEventListener('click', onClick);
}, [count]);
이렇게만 해도 문제는 없다.
하지만 이 코드 때문에 컴포넌트가 렌더링 될 때 마다 매번 eventListener가 등록되었다가 삭제된다.
왜냐하면 useEffect는 ComponentDidMount, ComponentDidUpdate, ComponentWillUnmount 이 3개의 생명주기를 대체하는 훅이기 때문이다.
당장의 count 하나만 의존성으로 관리한다면 그렇게 큰 문제는 일어나지 않을 수 있지만 아래와 같은 이벤트라면?
function CanvasEditor({ scale, tool, user, color }) {
const [x, setX] = useState(0);
const [y, setY] = useState(0);
const [dragging, setDragging] = useState(false);
useEffect(() => {
function onMouseMove(e) {
if (!dragging) return;
console.log(
'x:', x,
'y:', y,
'scale:', scale,
'tool:', tool,
'user:', user.name,
'color:', color
);
}
window.addEventListener('mousemove', onMouseMove);
return () => window.removeEventListener('mousemove', onMouseMove);
// dependency explosion
}, [x, y, dragging, scale, tool, user, color]);
}
x, y, dragging, scale, tool 등 많은 변수들이 의존성 배열에 들어가있고 이 중 하나라도 변경되면 생명주기를 다시 수행하게 되면서 리스너가 매번 등록&해제 될 것이다.
useEffectEvent
위와같은 문제를 해결하기 위해 나온 훅이 useEffectEvent이다.
useEffectEvent 훅의 목적은 항상 최신 값에 접근할 수 있는 안정적인 콜백 이다.
const onSomething = useEffectEvent(callback)
사용 방법은 간단하다.
useEffectEvent 훅의 매개변수로 callback 함수를 넘겨주기만 하면 된다.
반환값은 Effect Event Function이다.
Effect Event Function을 반환하기 때문에 useEffect, useLayoutEffect, useInsertionEffect 훅의 내부에서 호출할 수 있다.
사용 예시는 아래와 같다.
import { useEffectEvent } from 'react';
function MyComponent() {
const [count, setCount] = useState(0);
const handleClick = useEffectEvent(() => {
console.log(count); // 항상 최신 count 출력
});
useEffect(() => {
window.addEventListener('click', handleClick);
return () => window.removeEventListener('click', handleClick);
}, []); // 의존성 배열 없어도됨
}
count가 변경될 때 마다 리스너가 매번 등록&삭제되는 문제가 해결되고 handleclick 함수는 항상 최신 count값을 참조할 수 있게 되었다.
주의사항
useEffectEvent로 만든 함수는 반드시 useEffect 계열 훅 안에서만 호출해야 한다.
때문에 사용할 useEffect 바로 위에 선언하는 것이 좋다.
다른 컴포넌트나 훅에 함수를 넘길 수 없다.
useEffectEvent는 의존성 배열을 줄이기 위한 함수가 아니다.
useEffectEvent는 오로지 stale closure 방지용이다.
반드시 이벤트 핸들러 내부에서만 동작하는 로직으로 작성해야 한다.
React State를 직접 변경하는 이벤트에 이 훅을 사용하는 것은 부적합하다.
'React' 카테고리의 다른 글
| [React] Redux를 사용해서 간편하게 모달(modal) 구현하기 (0) | 2024.03.13 |
|---|---|
| [React] 컴포넌트의 생명주기(Lifecycle) (0) | 2023.02.10 |
| [React] 프로퍼티에 필수, 기본값 지정 / 자식 프로퍼티 사용 (0) | 2023.01.13 |
| [React] 다양한 자료형 프로퍼티 사용하기 (0) | 2023.01.06 |
| [React] 프로퍼티(props) 기본 (0) | 2023.01.04 |
