외부 시스템과 컴포넌트를 동기화하는 React Hook
Effect의 로직이 포함된 함수
정리 함수를 반환할 수 있음 (optional)
컴포넌트가 DOM에 추가된 이후
에 setup 함수 실행
의존성의 변화에 따라 리렌더링 되었을 경우, 정리함수를 반환할 경우 이전 렌더링에 사용된 값으로 정리함수를 실행한 후, 새로운 값으로 setup 함수 실행
컴포넌트가 DOM에서 제거된 경우에도 정리 함수 실행
setup
코드 내에서 참조된 모든 반응형 값의 배열**
반응형 값 : props, state, 컴포넌트 내부에 선언된 모든 변수나 함수 등
lint는 모든 반응형 값들이 의존성에 제대로 명시되어 있는지 검증
각각의 의존성들을 [Object.is](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is)
로 이전 값과 비교 (얕은 복사)
의존성을 생략할 경우, Effect는 컴포넌트가 리렌더링될 때마다 실행
useEffect(setup, dependencies?)
useEffect
대신 [useLayoutEffect](https://ko.react.dev/reference/react/useLayoutEffect)
사용 (예를 들어, 툴팁 배치 등 화면에 어떤 변화를 주는 경우)useEffect
대신 [useLayoutEffect](https://ko.react.dev/reference/react/useLayoutEffect)
를 사용일부 컴포넌트들은 페이지에 표시되는 동안 네트워크나 브라우저 API 등과 연결이 유지되어야함
외부 시스템 : React에 의해 제어되지 않는 모든 코드
import { useEffect } from 'react';
import { createConnection } from './chat.js';
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
마운트 : 컴포넌트가 화면에 추가되었을 때 setup 함수 실행
리렌더링 : 의존성이 변경될 경우, 컴포넌트가 리렌더링 될 때마다 아래 동작 수행
Effect를 자주 작성해야 한다면 컴포넌트가 의존하고 있는 공통적인 동작들을 커스텀 Hook으로 추출해야 한다는 신호일 수 있음
커스텀 Hook은 Effect의 로직을 조금 더 선언적인 API로 보일 수 있도록 숨겨줌
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
// ...
}
function useChatRoom({ serverUrl, roomId }) {
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId, serverUrl]);
}
function ChatRoom({ roomId }) {
const [serverUrl, setServerUrl] = useState('https://localhost:1234');
useChatRoom({
roomId: roomId,
serverUrl: serverUrl
});
// ...
컴포넌트의 prop 또는 state를 외부 시스템과 동기화해야할 때가 있음
이 컴포넌트의 state를 현재 React 컴포넌트의 state와 일치하도록 하기 위해 Effect를 사용할 수 있음
이 Effect는 map-widget.js
에 정의된 MapWidget
클래스의 인스턴스 생성
Map
컴포넌트의 zoomLevel
prop을 변경할 때, Effect는 해당 클래스 인스턴스의 setZoom()
을 호출하여 동기화
import { useRef, useEffect } from 'react';
import { MapWidget } from './map-widget.js';
export default function Map({ zoomLevel }) {
const containerRef = useRef(null);
const mapRef = useRef(null);
useEffect(() => {
if (mapRef.current === null) {
mapRef.current = new MapWidget(containerRef.current);
}
const map = mapRef.current;
map.setZoom(zoomLevel);
}, [zoomLevel]);
return (
<div
style={{ width: 200, height: 200 }}
ref={containerRef}
/>
);
}
컴포넌트에 데이터를 페칭하기 위해 Effect를 사용할 수 있음
ignore 변수의 초기값을 false로 설정하고, 정리 함수에서 true로 설정하는 로직 → race condition에 빠지지 않도록 보장
⇒ 네트워크 요청을 보낸 순서와 응답을 반는 순서가 다르게 동작할 수 있으므로 이러한 처리가 필요
Effect에서 직접 데이터 페칭 로직을 작성하면 나중에 캐싱 기능이나 서버 렌더링과 같은 최적화를 추가하기 어려워짐.
custom Hook이나 라이브러리를 사용하는 편이 더 간단.
import { useState, useEffect } from 'react';
import { fetchBio } from './api.js';
export default function Page() {
const [person, setPerson] = useState('Alice');
const [bio, setBio] = useState(null);
useEffect(() => {
let ignore = false;
setBio(null);
fetchBio(person).then(result => {
if (!ignore) {
setBio(result);
}
});
return () => {
ignore = true;
};
}, [person]);
// ...
Effect 내부에서 fetch
호출을 작성하는 것은 클라이언트 사이드 앱에서 가장 인기 있는 방법
But, 매우 수동적인 접근 방식이며, 큰 단점 존재
Effect 코드에서 사용하는 모든 반응형 값은 의존성으로 선언되어야함
Effect의 의존성 배열은 코드에 의해 결정됨 (개발자 선택 ❌)
의존성을 제거하려면 그것이 의존성이 되지 않아야함을 린터에 증명
→ 컴포넌트 밖으로 이동하여 그것이 반응적이지 않고 리렌더링될 때 변경되지 않을 것임을 증명
// before
function ChatRoom({ roomId }) { // 이것은 반응형 값입니다
const [serverUrl, setServerUrl] = useState('https://localhost:1234'); // 이것도 반응형 값입니다
useEffect(() => {
const connection = createConnection(serverUrl, roomId); // 이 Effect는 이 반응형 값들을 읽습니다
connection.connect();
return () => connection.disconnect();
}, [serverUrl, roomId]); // ✅ 그래서 이 값들을 Effect의 의존성으로 지정해야 합니다
// ...
}
// after
const serverUrl = 'https://localhost:1234'; // 더 이상 반응형 값이 아님
function ChatRoom({ roomId }) {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, [roomId]); // ✅ 모든 의존성이 선언됨
// ...
}
의존성이 비어있는 Effect는 props나 state가 변경되도 다시 실행되지 않음
const serverUrl = 'https://localhost:1234'; // 더 이상 반응형 값이 아님
const roomId = 'music'; // 더 이상 반응형 값이 아님
function ChatRoom() {
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => connection.disconnect();
}, []); // ✅ 모든 의존성이 선언됨
// ...
}
count
가 반응형 값이므로 반드시 의존성 배열에 추가해야함
count
가 변경될 때마다 Effect가 정리된 후 다시 설정되는 것을 무한 반복
function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(count + 1); // 초마다 카운터를 증가시키고 싶습니다...
}, 1000)
return () => clearInterval(intervalId);
}, [count]); // 🚩 ... 하지만 'count'를 의존성으로 명시하면 항상 인터벌이 초기화됩니다.
// ...
}
state 변경함수를 setCount에 추가
import { useState, useEffect } from 'react';
export default function Counter() {
const [count, setCount] = useState(0);
useEffect(() => {
const intervalId = setInterval(() => {
setCount(c => c + 1); // ✅ State 업데이터를 전달
}, 1000);
return () <=> clearInterval(intervalId);
}, []); // ✅ 이제 count는 의존성이 아닙니다
return <h1>{count}</h1>;
}
렌더링 중에 생성된 객체에 의존하는 경우, 너무 자주 실행될 수 있음
매 렌더링 후에 다시 연결됨 (렌더링마다 options
객체가 다름)
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
const options = { // 🚩 이 객체는 재 렌더링 될 때마다 새로 생성됩니다
serverUrl: serverUrl,
roomId: roomId
};
useEffect(() => {
const connection = createConnection(options); // 객체가 Effect 안에서 사용됩니다
connection.connect();
return () => connection.disconnect();
}, [options]); // 🚩 결과적으로, 의존성이 재 렌더링 때마다 다릅니다
// ...
객체가 반응형 값에 의존 ❌ → 객체를 컴포넌트 외부로 이동
객체가 반응형 값에 의존 → 객체를 Effect 내에서 생성
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
const options = {
serverUrl: serverUrl,
roomId: roomId
};
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
...
}
렌더링 중에 생성된 함수에 의존하는 경우, 너무 자주 실행될 수 있음
매 렌더링 후에 다시 연결됨 (렌더링마다 createOptions
함수가 다름)
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
function createOptions() { // 🚩 이 함수는 재 렌더링 될 때마다 새로 생성됩니다
return {
serverUrl: serverUrl,
roomId: roomId
};
}
useEffect(() => {
const options = createOptions(); // 함수가 Effect 안에서 사용됩니다
const connection = createConnection();
connection.connect();
return () => connection.disconnect();
}, [createOptions]); // 🚩 결과적으로, 의존성이 재 렌더링 때마다 다릅니다
// ...
리렌더링마다 함수를 처음부터 생성하는 것 자체로는 문제가 되지 않지만, Effect의 의존성으로 사용하는 경우 리렌더링 후마다 Effect가 다시 실행됨
→ Effect 내에서 함수 선언
const serverUrl = 'https://localhost:1234';
function ChatRoom({ roomId }) {
const [message, setMessage] = useState('');
useEffect(() => {
function createOptions() {
return {
serverUrl: serverUrl,
roomId: roomId
};
}
const options = createOptions();
const connection = createConnection(options);
connection.connect();
return () => connection.disconnect();
}, [roomId]);
...
}
개발 환경에서 Strict Mode가 활성화되면 React는 실제 설정 이전에 설정과 정리를 한번 더 실행함 → Effect 로직이 올바르게 구현되었는지 확인하는 스트레스 테스트
사용자가 설정이 한번 호출되는 것(배포 환경과 같이)과 설정 → 정리 → 설정 순서로 호출되는 것을 구별할 수 없어야 한다는 것
의존성 배열을 명시했음에도 Effect가 반복해서 실행된다면 의존성이 렌더링마다 다르기 때문
렌더링마다 다른 의존성을 찾아냈다면 다음 중 하나의 방법으로 수정할 수 있음
Effect가 무한 반복되려면 다음 두 가지 조건이 충족되어야 합니다..
문제를 해결하기 전에 Effect가 외부 시스템에 연결되어 있는지 자문하기
외부 시스템이 없다 → Effect를 제거하는 방법 고려
외부 시스템과 동기화 중이다 → Effect가 state를 언제 어떤 조건에서 업데이트해야 하는지에 대해 고려
정리 함수는 마운트 해제 시 뿐만 아니라 변경된 종속성으로 인한 모든 리렌더링 전에 실행
개발 환경에서는 컴포넌트가 마운트된 직후에 한 번 더 설정과 정리 실행
정리 로직은 설정 로직과 ‘대칭’이어야 하며 설정이 수행한 것을 중지하거나 되돌릴 수 있어야함
useEffect(() => {
const connection = createConnection(serverUrl, roomId);
connection.connect();
return () => {
connection.disconnect();
};
}, [serverUrl, roomId]);
Effect가 브라우저가 화면을 그리는 것
을 차단해야 하는 경우 useEffect
를 [useLayoutEffect](https://ko.react.dev/reference/react/useLayoutEffect)
로 대체
대부분의 Effect에는 필요하지 않음
브라우저 페인팅 이전에 Effect를 실행하는 것이 중요한 경우에만 필요