1. Effect란 무엇이며 이벤트와는 어떤게 다른가?
2. Effect가 필요하지 않을 수도 있다.
3. Effect 작성 방법
4. 개발 환경에서 두 번씩 실행되는 Effect를 처리하는 방법은 무엇인가?
5. 한 곳에 모으기
KeyNote
- 이 글에서 대문자로 시작하는 “Effect”는 위의 React에 한정된 정의,
즉, 렌더링으로 인해 발생하는 사이드 이펙트를 나타낸다.
이 후로 더 넓은 프로그래밍 개념을 언급할 때는 “사이드 이펙트”라고 하겠다.
2-1. Effect 작성 방법
Effect를 작성하려면 다음 세 단계를 따라가자.
Effect를 선언한다.
기본적으로 Effect는 모든 렌더링 후에 실행된다.
Effect의 의존성을 명시한다.
대부분의 Effect는 렌더링 할 때마다가 아니라 필요할 때만 다시 실행해야 한다.
예를 들어, 페이드 인 애니메이션은 컴포넌트가 나타날 때만 발동되어야 한다.
대화방 연결 및 해제는 컴포넌트가 나타났다가 사라지거나 대화방이 변경될 때만 발생해야한다.
필요한 경우 클린업을 추가한다.
일부 Effect는 수행중이던 작업을 중지, 취소 또는 정리하는 방법을 명시해야한다.
예를 들어, “connect”에는 “disconnect”가 필요하고 “subscribe”에는 “unsubscribe”가 필요하며 “fetch”에는 “cancel”또는 “ignore”가 필요하다.
! 주의 !
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
});
[]
의존성 배열이 있는 경우의 동작은 다르다.useEffect(() => {
// This runs after every render
// 렌더시마다 실행됩니다.
});
useEffect(() => {
// This runs only on mount (when the component appears)
// 오직 마운트시(컴포넌트가 나타날 때)에만 실행됩니다.
}, []);
useEffect(() => {
// This runs on mount *and also* if either a or b have changed since the last render
// 마운트시 뿐만 아니라 a 또는 b가 직전 렌더와 달라졌을 때에도 실행됩니다.
}, [a, b]);
4-1. React가 아닌 위젯 제어하기
setZoomLevel()
메서드가 있으며, 확대/축소 수준을 React 코드의 zoomLevel
state 변수와 동기화하고 싶다. Effect는 다음과 비슷할 것이다.useEffect(() => {
const map = mapRef.current;
map.setZoomLevel(zoomLevel);
}, [zoomLevel]);
setZoomLevel
을 두 번 호출해도 아무 작업도 수행하지 않기 때문에 문제가 되지 않는다.<dialog>
의 showModal
메서드는 두 번 호출하면 에러를 던잔다.useEffect(() => {
const dialog = dialogRef.current;
dialog.showModal();
return () => dialog.close();
}, []);
showModal()
을 호출한 다음 즉시 close()
를 호출하고, 다시 showModal()
을 호출한다.showModal()
을 한 번 호출하는 것과 체감상 동일하다.4-2. 이벤트 구독하기
useEffect(() => {
function handleScroll(e) {
console.log(window.scrollX, window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);
addEventListener()
를 호출한 다음 즉시 removeEventListener()
를 호출한다.addEventListener()
를 사용함으로써, 한 번에 하나의 구독만 활성화 되도록 한다.addEventListener()
를 한 번만 호출하는 것과 체감상 동일하다.4-3. 트리거링 애니메이션
useEffect(() => {
const node = ref.current;
node.style.opacity = 1; // Trigger the animation
// 애니메이션 촉발
return () => {
node.style.opacity = 0; // Reset to the initial value
// 초기값으로 재설정
};
}, []);
1
이었다가, 0
이었다가, 다시 1
로 설정된다.1
을 한 번만 설정하는 것과 체감상 동일하다.4-4. 데이터 페칭하기
useEffect(() => {
let ignore = false;
async function startFetching() {
const json = await fetchTodos(userId);
if (!ignore) {
setTodos(json);
}
}
startFetching();
return () => {
ignore = true;
};
}, [userId]);
userId
가 'Alice'
에서 'Bob'
으로 변경되면 클린업은 'Alice'
응답이 'Bob'
이후에 도착하더라도 이를 무시하도록 한다.ignore
변수의 복사본이 true
로 설정된다.if (!ignore)
검사 덕분에 state에 영향을 미치지 않는다.function TodoList() {
const todos = useSomeDataLibrary(`/api/user/${userId}/todos`);
// ...
4-5. 분석 전송
useEffect(() => {
logVisit(url); // Sends a POST request
}, [url]);
4-6. Effect가 아님: 애플리케이션 초기화하기
if (typeof window !== 'undefined') { // Check if we're running in the browser.
// 실행환경이 브라우저인지 여부 확인
checkAuthToken();
loadDataFromLocalStorage();
}
function App() {
// ...
}
4-7. Effect가 아님: 제품 구매하기
useEffect(() => {
// 🔴 Wrong: This Effect fires twice in development, exposing a problem in the code.
// 🔴 틀렸습니다: 이 Effect는 개발모드에서 두 번 실행되며, 문제를 일으킵니다.
fetch('/api/buy', { method: 'POST' });
}, []);
/api/buy
요청을 구매 버튼 이벤트 핸들러로 이동한다.function handleClick() {
// ✅ Buying is an event because it is caused by a particular interaction.
// ✅ 구매는 특정 상호작용으로 인해 발생하므로 이벤트입니다.
fetch('/api/buy', { method: 'POST' });
}
import { useState, useEffect } from 'react';
function Playground() {
const [text, setText] = useState('a');
useEffect(() => {
function onTimeout() {
console.log('⏰ ' + text);
}
console.log('🔵 Schedule "' + text + '" log');
const timeoutId = setTimeout(onTimeout, 3000);
return () => {
console.log('🟡 Cancel "' + text + '" log');
clearTimeout(timeoutId);
};
}, [text]);
return (
<>
<label>
What to log:{' '}
<input
value={text}
onChange={e => setText(e.target.value)}
/>
</label>
<h1>{text}</h1>
</>
);
}
export default function App() {
const [show, setShow] = useState(false);
return (
<>
<button onClick={() => setShow(!show)}>
{show ? 'Unmount' : 'Mount'} the component
</button>
{show && <hr />}
{show && <Playground />}
</>
);
}
Schedule "a" log
, Cancel "a" log
, Schedule "a" log
의 세 가지 로그가 표시된다. 3초 후에 a
라는 로그도 표시된다.abc
라고 입력하라."ab" log
와 Cancel "ab" log
, Schedule "abc" log
가 바로 표시될 것이다.abcde
를 빠르게 입력해 보자. 3초 후에 어떤 일이 일어날까?abcde
가 아닌 일련의 (a, ab, abc, abcd, abcde)
가 표시되어야 한다.text
값을 “캡처”한다.text
state가 변경되더라도, text = 'ab'
인 렌더링의 Effect는 항상 'ab'
를 표시한다.[]
)은 컴포넌트 “마운팅”, 즉,화면에 추가되는 시점에 대응한다.https://developer.mozilla.org/ko/