- 이벤트 핸들러는 같은 상호 작용을 다시 수행할 때만 다시 실행된다.
- 이벤트 핸들러와 달리 Effect는 prop 또는 state 변수와 같은 일부 값을 마지막 렌더링 때와 다른 값으로 읽게 되면 다시 동기화된다.
- 이 챕터에서는 값에 대한 응답으로 다시 실행되는 Effect와 그렇지 않은 Effect의 혼합이 필요한 경우에 어떻게 이를 수행하는지 알아본다.
Effects run whenever synchronization is needed(Effect는 동기화가 필요할 때마다 실행됩니다)
- 이벤트 핸들러는 특정 상호 작용에 대한 처리를 할 때 사용한다.
- Effect는 코드를 사용자가 수행한 특정 상호작용과는 무관하게, 코드가 실행되었을 때 실행되는 내용에 사용한다.
Reactive values and reactive logic(반응형 값 및 반응형 로직)
- 이벤트 핸들러는 항상 "수동"으로 촉발시킨다.
- 이벤트 핸들러 내부의 로직은 반응형이 아니다. 사용자가 동일한 상호작용을 다시 수행하지 않는 한 다시 실행되지 않는다. 즉, 이벤트 핸들러는 변경에 "반응"하지 않고 반응형 값을 읽을 수 있다.
- Effect는 "자동"으로 동기화 상태를 유지하는 데 필요한 만큼 자주 다시 실행된다.
- Effects 내부의 로직은 반응형이다. Effect에서 반응형 값을 읽는 경우 의존성으로 지정해야 한다. 그럼 다음 리렌더링으로 인해 해당 값이 변경되면 React는 새 값으로 Effect의 로직을 다시 실행한다.
Extracting non-reactive logic out of Effects(Effect에서 비반응형 로직 추출하기)
function ChatRoom({ roomId, theme }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
showNotification('Connected!', theme);
});
connection.connect();
return () => {
connection.disconnect()
};
}, [roomId, theme]); // ✅ All dependencies declared
// ...
- 위의 예제에서는 채팅에 연결할 때, props로 전달된 테마를 읽어 색상으로 알림을 표시한다.
- 하지만, roomId가 변경되면, 예상대로 채팅이 다시 연결되지만, theme도 의존성이기 때문에 dark와 light 테마 사이를 전화할 때마다 채팅이 또다시 연결되는 문제가 발생한다.
- 즉, 반응형 Effect안에 있더라도, 반응형 Effect가 되는 것을 원하지 않는다는 것이다.
Declaring an Effect Event(Effect Event 선언하기)
- 위 에제에서 비반응형 로직을 Effect에서 추출하려면 useEffectEvent라는 특수 Hook을 사용할 수 있다.
function ChatRoom({ roomId, theme }) {
const onConnected = useEffectEvent(() => {
showNotification('Connected!', theme);
});
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.on('connected', () => {
onConnected();
});
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ All dependencies declared
// ...
- 위 코드와 같이 수정을 할 때, Effect Event는 반응형 이벤트가 아니므로 의존성에서 생략해야 한다.
- Effect Event는 이벤트 핸들러와 매우 유사하다고 생각할 수 있는데, 가장 큰 차이점은 이벤트 핸들러는 사용자 상호작용에 대한 응답으로 실행되는 반면, Effect Event는 Effect에서 사용자가 촉발한다는 점이다.
Reading latest props and state with Effect Events(Effect Event로 최신 props 및 state 읽기)
* 이 섹션에서는 아직 안정된 버전의 React로 출시되지 않은 실험적인 API에 대해 설명한다.
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
useEffect(() => {
logVisit(url, numberOfItems);
}, [url]); // 🔴 React Hook useEffect has a missing dependency: 'numberOfItems'
// ...
}
- 위 예제는 사용자가 페이지를 방문했을 때, 페이지 방문 기록과 장바구니의 상품 개수를 표시하는 컴포넌트이다.
- logVisit 호출이 numberOfItems에 반응하는 것을 원치 않을 때(사용자가 장바구니에 무언가를 넣고 numberOfItems가 변경되는 것이 사용자가 페이지를 다시 방문했다는 것을 의미하지 않기 때문) Effect Event를 사용할 수 있다.
- 즉, 페이지 방문은 어떤 의미에서 "이벤트"이기 때문에, 아래와 같이 수정할 수 있다.
function Page({ url }) {
const { items } = useContext(ShoppingCartContext);
const numberOfItems = items.length;
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
onVisit(url);
}, [url]); // ✅ All dependencies declared
// ...
}
- 이제 onVisit는 Effect Event이고, 그 안의 코드는 반응형이 아니기 때문에 변경 시 주변 코드가 다시 실행될 것을 걱정할 필요 없이 numberOfItems(또는 다른 반응형 값)을 사용할 수 있다.
- 반면, Effect 내부의 코드는 url prop을 사용하므로 url이 변경될 때마다, 재실행된다. 즉 Effect 자체는 반응형으로 유지가 된다.
- 결과적으로 url이 변경될 때마다 logVisit을 호출하고 항상 최신 numberOfItems를 읽지만, numberOfItems가 자체적으로 변경되면 코드가 다시 실행되지는 않는다.
- onVisit 함수를 매개변수 없이 호출할 수 있지만, Effect Event에 명시적으로 전달하는 것이 좋다. 왜냐하면 url을 가진 페이지를 방문하는 것이 사용자 관점에서 별도의 "이벤트"를 구성하는 의미이기 때문이다.
- 특히, Effect 내부에 비동기 로직이 있는 경우에는 매개변수를 전달하는 것이 매우 중요하다.
const onVisit = useEffectEvent(visitedUrl => {
logVisit(visitedUrl, numberOfItems);
});
useEffect(() => {
setTimeout(() => {
onVisit(url);
}, 5000); // Delay logging visits
}, [url]);
- 왜냐하면, onVisit에 전달되는 매개변수 url은(이미 변경되었을 수 있는) 최신 url에 해당하지만, visitedUrl은 원래 이 Effect(및 onVisit 호출)를 실행하게 만든 url에 해당하기 때문이다.
Limitations of Effect Event(Effect Event의 제한사항)
* 이 섹션에서는 아직 안정된 버전의 React로 출시되지 않은 실험적인 API에 대해 설명한다.
- Effect Event는 사용할 수 있는 방법이 매우 제한적이다.
1. Effect 내부에서만 호출할 수 있다.
2. 다른 컴포넌트나 Hook에 전달하면 안된다.
// 다른 컴포넌트에 useEffectEvent를 전달하는 경우
function Timer() {
const [count, setCount] = useState(0);
const onTick = useEffectEvent(() => {
setCount(count + 1);
});
useTimer(onTick, 1000); // 🔴 Avoid: Passing Effect Events
return <h1>{count}</h1>
}
function useTimer(callback, delay) {
useEffect(() => {
const id = setInterval(() => {
callback();
}, delay);
return () => {
clearInterval(id);
};
}, [delay, callback]); // Need to specify "callback" in dependencies
}
// 대신 항상 Effect Event를 사용하는 Effect 바로 옆에 Effect Event를 선언하는 방법
function Timer() {
const [count, setCount] = useState(0);
useTimer(() => {
setCount(count + 1);
}, 1000);
return <h1>{count}</h1>
}
function useTimer(callback, delay) {
const onTick = useEffectEvent(() => {
callback();
});
useEffect(() => {
const id = setInterval(() => {
onTick(); // ✅ Good: Only called locally inside an Effect
}, delay);
return () => {
clearInterval(id);
};
}, [delay]); // No need to specify "onTick" (an Effect Event) as a dependency
}
- Effect Event는 Effect 코드의 비반응형 "조각"이기 때문에, Effect Event는 이를 사용하는 Effect 옆에 있어야 한다.
* 참고 : React 공식문서(https://react-ko.dev/learn)
'이것저것 스터디📚 > React - 공식 문서' 카테고리의 다른 글
React 공식문서 -Reusing Logic with Custom Hooks(커스텀 훅으로 로직 재사용하기) (0) | 2023.08.24 |
---|---|
React 공식문서 -Removing Effect Dependencies(Effect 의존성 제거하기) (0) | 2023.08.23 |
React 공식문서 -Lifecycle of Reactive Effects(반응형 Effect의 생명주기) (0) | 2023.08.23 |
React 공식문서 -You Might Not Need an Effect(Effect가 필요하지 않을 수도 있습니다) (0) | 2023.08.17 |
React 공식문서 -Synchronizing with Effects(Effect와 동기화 하기) (0) | 2023.08.17 |