useEffect 심화
의존성 배열과 cleanup의 실행 시점을 구분하고 로그·타이머·fetch 예제별 정리 범위와 예상 결과를 확인합니다.
앞 절에서는 useState 훅의 심화 개념을 다루며 상태를 더 견고하고 효율적으로 관리하는 방법을 알아보았습니다.
이번에는 리액트 함수형 컴포넌트의 부수 효과(Side Effect)를 다루는 useEffect가 작업을 시작하고 정리하는 시점을 살펴보겠습니다.
우리는 이미 3장에서 useEffect의 기본 역할과 마운트/업데이트/언마운트 시점에서의 동작 방식을 간략하게 살펴보았습니다.
입력이 바뀌면 이전 타이머는 어떻게 될까요? 아래에서는 의존성 배열(deps 배열)에 따라 setup이 실행되는 시점과 클린업(Clean-up) 함수가 실제로 정리하는 범위를 구분합니다. 로그만 남기는 예제, 대기 타이머를 해제하는 예제, 진행 중 fetch를 중단하는 발췌를 같은 동작으로 읽지 않도록 확인해 보겠습니다.
useEffect의 핵심: 의존성 배열 (deps 배열)
useEffect 훅의 두 번째 인자인 의존성 배열(Dependency Array)은 useEffect의 동작 방식을 결정하는 가장 중요한 요소입니다.
이 배열에 어떤 값을 넣느냐에 따라 useEffect 콜백 함수가 언제 다시 실행될지가 결정됩니다.
useEffect(() => {
// 이펙트 콜백 함수: 부수 효과 로직
console.log('이펙트 실행!');
return () => {
// 클린업 함수: 부수 효과 정리 로직
console.log('클린업 실행!');
};
}, [dep1, dep2, ...]); // 의존성 배열 (deps)deps 배열의 역할
deps배열에 있는 값들 중 어떤 하나라도 이전 렌더링 이후 변경되었다면,useEffect콜백 함수가 다시 실행됩니다.deps배열은 리액트에게 이 이펙트가 의존하는 값들은 여기에 있으니, 이 값들이 변할 때만 다시 실행해줘라고 알려주는 역할을 합니다.
deps 배열이 없는 경우
deps 배열을 아예 생략하면, useEffect는 컴포넌트의 렌더 결과가 커밋될 때마다 실행됩니다.
- 컴포넌트의 모든 렌더링 주기마다 특정 로직을 실행해야 할 때 (매우 드뭄).
- 주의: 이 안에서
state를 직접 변경하면 무한 루프에 빠질 수 있습니다. 예를 들어,setState(newValue)를 직접 호출하면state변경 -> 리렌더링 ->useEffect재실행 ->state변경 ... 무한 반복.
빈 deps 배열
deps 배열을 빈 배열([])로 전달하면, useEffect는 일반적인 최초 마운트에서 setup을 실행하고 반응형 의존 값 변경으로는 재실행하지 않습니다. 개발 Strict Mode는 추가 setup→cleanup→setup 점검을 할 수 있습니다.
이후의 일반적인 리렌더링에서는 의존 값 변화로 재실행되지 않습니다. 개발 환경의 추가 점검은 이 규칙과 구별합니다.
이는 클래스 컴포넌트의 componentDidMount와 유사합니다.
- 초기 데이터 로딩 (API 호출).
- 이벤트 리스너 등록 (클린업에서 해제 필요).
- 구독 설정 (클린업에서 해제 필요).
- DOM 직접 조작 (차트 라이브러리 초기화 등).
deps 배열)
src/components/UserFetcher.js를 만듭니다. 실제 HTTP 요청 없이 Promise 안의 타이머로 1.5초 기다린 뒤 고정된 사용자를 표시합니다. cleanup은 로그만 남깁니다. 타이머나 Promise를 취소하지 않고, 늦은 결과를 무시하는 guard도 없는 예제입니다.
import React, { useState, useEffect } from 'react';
function UserFetcher() {
const [user, setUser] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
console.log('UserFetcher setup: 사용자 정보 로딩을 시작합니다.');
setLoading(true);
const fetchUser = async () => {
try {
// 실제 HTTP 요청 없이 지연 Promise로 고정 사용자를 표시합니다.
await new Promise(resolve => setTimeout(resolve, 1500)); // 1.5초 지연
const fetchedUser = { id: 1, name: '김리액트', email: '[email protected]' };
setUser(fetchedUser);
setError(null);
} catch (err) {
setError('사용자 정보를 불러오는 데 실패했습니다.');
setUser(null);
} finally {
setLoading(false);
}
};
fetchUser();
// cleanup: 정리 시점을 로그로 확인합니다.
return () => {
console.log('UserFetcher cleanup: 정리 시점을 확인합니다.');
// 로그만 남깁니다. 지연 Promise 취소나 늦은 결과 무시는 하지 않습니다.
};
}, []); // 일반 마운트 때 setup; 개발 Strict Mode에서는 추가 점검 가능
if (loading) return <p style={{ color: '#555' }}>사용자 정보 로딩 중...</p>;
if (error) return <p style={{ color: 'red' }}>에러: {error}</p>;
return (
<div style={{ border: '1px solid #673AB7', padding: '20px', margin: '20px', borderRadius: '8px' }}>
<h2>사용자 프로필</h2>
{user && (
<div>
<p>이름: {user.name}</p>
<p>이메일: {user.email}</p>
</div>
)}
</div>
);
}
export default UserFetcher;특정 값들을 deps 배열에 포함하는 경우
가장 일반적인 사용 패턴입니다.
deps 배열에 포함된 값(prop, state, 함수 등) 중 하나라도 변경될 때마다 useEffect 콜백이 재실행됩니다.
이는 클래스 컴포넌트의 componentDidUpdate와 유사합니다.
- 특정
prop이나state가 변경될 때마다 데이터를 다시 로드해야 할 때 (예: 검색어 변경 시 검색 결과 업데이트). - 특정
prop이나state에 따라 다른 부수 효과를 실행해야 할 때.
src/components/SearchComponent.js를 만듭니다. 검색어가 2글자 이상이면 500ms 뒤 그 검색어를 넣은 고정 문자열을 만듭니다. 검색어가 다시 바뀌면 이전 setup의 cleanup이 먼저 실행되어 아직 대기 중인 타이머를 해제합니다. 실제 HTTP 검색이나 서버 작업을 취소하는 예제는 아닙니다.
import React, { useState, useEffect } from 'react';
function SearchComponent() {
const [searchTerm, setSearchTerm] = useState('');
const [searchResults, setSearchResults] = useState([]);
const [loading, setLoading] = useState(false);
// (1) searchTerm이 변경될 때마다 이펙트 실행
useEffect(() => {
if (searchTerm.length < 2) { // 2글자 미만일 땐 검색하지 않음
setSearchResults([]);
setLoading(false);
return;
}
setLoading(true);
console.log(`"${searchTerm}" 검색 타이머를 설정했습니다.`);
// 입력이 멈춘 뒤 고정 검색 결과를 만듭니다.
const timer = setTimeout(() => { // 마지막 입력 뒤 0.5초 기다리는 디바운싱
const mockResults = [
`검색 결과: ${searchTerm} - 아이템 A`,
`검색 결과: ${searchTerm} - 아이템 B`,
`검색 결과: ${searchTerm} - 아이템 C`,
].filter(item => item.includes(searchTerm)); // 고정 문자열에 검색어가 포함되어 있습니다.
setSearchResults(mockResults);
setLoading(false);
console.log(`"${searchTerm}" 검색 결과를 만들었습니다.`);
}, 500);
// ⭐️ (2) 클린업 함수: 이전 타이머 해제
return () => {
console.log(`"${searchTerm}" 이전 검색 타이머 정리.`);
clearTimeout(timer); // 아직 실행되지 않은 타이머 콜백만 막습니다.
};
}, [searchTerm]); // ⭐️ searchTerm이 변경될 때마다 이펙트 재실행
return (
<div style={{ border: '1px solid #FFC107', padding: '20px', margin: '20px', borderRadius: '8px' }}>
<h2>실시간 검색</h2>
<input
type="text"
placeholder="검색어를 입력하세요..."
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
style={{ padding: '8px', fontSize: '16px', borderRadius: '5px', border: '1px solid #ddd', width: '80%', marginBottom: '15px' }}
/>
{loading && <p>검색 중...</p>}
{!loading && searchResults.length === 0 && searchTerm.length >= 2 && <p>일치하는 결과가 없습니다.</p>}
{!loading && searchResults.length > 0 && (
<ul style={{ listStyle: 'none', padding: 0 }}>
{searchResults.map((result, index) => (
<li key={index} style={{ padding: '5px 0', borderBottom: '1px dotted #eee' }}>{result}</li>
))}
</ul>
)}
</div>
);
}
export default SearchComponent;[searchTerm]을deps배열에 넣었기 때문에searchTermstate가 변경될 때마다useEffect가 다시 실행됩니다.- 디바운싱(Debouncing)은 입력이 이어지는 동안 기다렸다가, 마지막 입력 후 0.5초가 지나면 작업하는 방식입니다. 여기서는 고정 결과를 만드는 타이머에 적용했습니다.
clearTimeout은 아직 실행되지 않은 콜백만 막으며, 이미 만들어진 결과나 서버의 작업을 되돌리지 않습니다.
useEffect의 클린업(Clean-up) 함수 심화
클린업 함수는 useEffect 콜백 함수가 반환하는 함수로, 리소스 정리 작업을 수행합니다.
클린업이 실행되는 시점
컴포넌트가 언마운트될 때: 의존성 배열의 유무와 관계없이 컴포넌트가 화면에서 제거되면 마지막 setup이 반환한 cleanup을 실행합니다. 그 setup이 cleanup을 반환하지 않았다면 실행할 정리 함수도 없습니다.
다음 setup이 실행되기 전: 의존 값이 바뀌어 Effect를 다시 실행할 때는 이전 setup의 cleanup을 먼저 실행합니다. 의존성 배열을 생략해 커밋마다 실행하는 경우에도 이전 cleanup 뒤 새 setup이 이어집니다. SearchComponent의 clearTimeout(timer)가 이 순서를 따릅니다.
개발 Strict Mode의 추가 점검: 추가 점검이 적용되면 setup→cleanup→setup 순서를 한 번 더 거칩니다. cleanup 로그가 보였다고 실제로 컴포넌트를 숨겼다고 단정할 수 없으며, 이 점검은 프로덕션의 추가 실행 규칙이 아닙니다.
Effect는 이전 연결을 닫은 뒤 새 연결을 연다 — 상태 추적
room-1과 room-2 연결에서 새 commit 뒤 이전 cleanup→새 setup, unmount 마지막 cleanup으로 자원 상태가 달라진다. 개발 Strict Mode 점검과 실제 제거를 구분한다.
EFFECT: REPLACE A RESOURCE
새 setup 전에 이전 Effect가 만든 자원을 정리합니다
구독 room-1→room-2는 정리 순서를 설명하는 예입니다. 의존성이 달라진 커밋 뒤 cleanup(room-1)이 먼저 실행되고 setup(room-2)이 이어집니다. 본문의 SearchComponent는 실제 HTTP 요청이 아니라 아직 대기 중인 타이머만 해제합니다. UserFetcher의 로그 전용 cleanup과는 정리 범위가 다릅니다.
SearchComponent · [searchTerm]
검색어 A: 500ms 타이머 설정
검색어 B: A 타이머 clearTimeout
B 타이머 설정
unmount: 마지막 타이머 해제2글자 미만이면 이전 대기 타이머를 정리하고 결과를 비우며 새 타이머는 만들지 않습니다. 이미 콜백이 실행한 뒤의 결과까지 되돌리는 취소는 아닙니다.
비동기 작업마다 다른 정리
UserFetcher의 cleanup은 로그만 남겨 1.5초 지연 Promise가 계속되며 늦은 결과 guard도 없습니다. fetch 발췌는 실제 endpoint와 의존 값이 필요합니다. AbortController는 진행 중 fetch·응답 읽기를 중단하지만 서버의 완료 작업이나 이미 반영한 화면을 되돌리지 않습니다.
Effect에서 읽는 반응형 값을 deps에 포함합니다. cleanup은 의존 값이 바뀌어 다음 setup을 실행하기 전과 마지막 unmount 때 실행합니다. 개발 Strict Mode의 추가 setup→cleanup→setup은 별도 점검입니다. cleanup 로그만으로 타이머·Promise가 취소되었다고 판단하지 않습니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| room 1 setup | resource: room 1 열림 / input: roomId 1 | roomId 1의 연결을 연다 | Effect 자원은 해당 setup의 소유다 |
| room 2 commit과 교체 | room 1: 종료 / room 2: 열림 | 새 값 commit 뒤 room 1 cleanup을 하고 room 2 setup을 한다 | 이전 자원을 먼저 정리한다 |
| unmount | resource: 없음 / component: 트리 제거 | 마지막 room 2 cleanup으로 연결이 없다 | Strict Mode의 개발 점검 cleanup은 실제 제거와 별개다 |
Effect는 이전 연결을 닫은 뒤 새 연결을 연다 — 상태 추적
room-1과 room-2 연결에서 새 commit 뒤 이전 cleanup→새 setup, unmount 마지막 cleanup으로 자원 상태가 달라진다. 개발 Strict Mode 점검과 실제 제거를 구분한다.
EFFECT: REPLACE A RESOURCE
새 setup 전에 이전 Effect가 만든 자원을 정리합니다
구독 room-1→room-2는 정리 순서를 설명하는 예입니다. 의존성이 달라진 커밋 뒤 cleanup(room-1)이 먼저 실행되고 setup(room-2)이 이어집니다. 본문의 SearchComponent는 실제 HTTP 요청이 아니라 아직 대기 중인 타이머만 해제합니다. UserFetcher의 로그 전용 cleanup과는 정리 범위가 다릅니다.
SearchComponent · [searchTerm]
검색어 A: 500ms 타이머 설정
검색어 B: A 타이머 clearTimeout
B 타이머 설정
unmount: 마지막 타이머 해제2글자 미만이면 이전 대기 타이머를 정리하고 결과를 비우며 새 타이머는 만들지 않습니다. 이미 콜백이 실행한 뒤의 결과까지 되돌리는 취소는 아닙니다.
비동기 작업마다 다른 정리
UserFetcher의 cleanup은 로그만 남겨 1.5초 지연 Promise가 계속되며 늦은 결과 guard도 없습니다. fetch 발췌는 실제 endpoint와 의존 값이 필요합니다. AbortController는 진행 중 fetch·응답 읽기를 중단하지만 서버의 완료 작업이나 이미 반영한 화면을 되돌리지 않습니다.
Effect에서 읽는 반응형 값을 deps에 포함합니다. cleanup은 의존 값이 바뀌어 다음 setup을 실행하기 전과 마지막 unmount 때 실행합니다. 개발 Strict Mode의 추가 setup→cleanup→setup은 별도 점검입니다. cleanup 로그만으로 타이머·Promise가 취소되었다고 판단하지 않습니다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| room 1 setup | resource: room 1 열림 / input: roomId 1 | roomId 1의 연결을 연다 | Effect 자원은 해당 setup의 소유다 |
| room 2 commit과 교체 | room 1: 종료 / room 2: 열림 | 새 값 commit 뒤 room 1 cleanup을 하고 room 2 setup을 한다 | 이전 자원을 먼저 정리한다 |
| unmount | resource: 없음 / component: 트리 제거 | 마지막 room 2 cleanup으로 연결이 없다 | Strict Mode의 개발 점검 cleanup은 실제 제거와 별개다 |
클린업의 중요성 및 사용 사례
setup에서 연 구독이나 리스너가 필요 없어졌는데도 남아 있으면 중복 동작이나 누수로 이어질 수 있습니다. cleanup에는 그 자원을 정리하는 동작을 직접 작성합니다. 함수를 반환했다는 사실만으로 타이머·Promise·요청이 자동으로 취소되는 것은 아닙니다.
주요 클린업 사용 사례타이머 해제: setTimeout, setInterval과 같은 타이머를 설정했을 때, 컴포넌트가 사라지면 타이머도 함께 멈춰야 합니다.
useEffect(() => {
const timer = setInterval(() => console.log('tick'), 1000);
return () => clearInterval(timer); // 타이머 해제
}, []);이벤트 리스너 제거: window, document, 또는 특정 DOM 요소에 이벤트 리스너를 직접 추가했을 때, 컴포넌트가 언마운트되면 해당 리스너를 제거해야 합니다.
useEffect(() => {
const handleResize = () => console.log('창 크기 변경');
window.addEventListener('resize', handleResize);
return () => window.removeEventListener('resize', handleResize); // 리스너 제거
}, []);구독 해제: WebSocket, RxJS 등 외부 라이브러리를 통해 데이터 스트림을 구독했을 때, 더 이상 필요 없을 때 구독을 해제해야 합니다.
useEffect(() => {
const subscription = someService.subscribe(data => console.log(data));
return () => subscription.unsubscribe(); // 구독 해제
}, []);진행 중인 fetch 중단: AbortController는 진행 중인 fetch와 응답 본문 읽기를 중단할 때 사용합니다. 이미 서버에 전달된 작업이나 화면에 반영한 결과를 되돌리지는 않습니다. Search의 대기 타이머 해제와는 다른 동작입니다.
아래는 참고 발췌입니다. 실제 /api/data endpoint, someDependency의 정의, 응답 상태별 처리와 결과를 사용할 문맥이 필요합니다. 이 절은 해당 API 서버를 제공하지 않으므로 아래 코드를 독립된 App 예제로 붙여 넣는 것으로 요청 성공을 확인할 수는 없습니다.
// 예시: 실제 endpoint와 의존 값이 필요한 fetch 발췌
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
fetch('/api/data', { signal })
.then(response => response.json())
.then(data => console.log(data))
.catch(error => {
if (error.name === 'AbortError') {
console.log('Fetch aborted');
} else {
console.error('Fetch error:', error);
}
});
return () => {
controller.abort(); // 다음 setup 전 또는 언마운트 때 진행 중 fetch를 중단
};
}, [someDependency]);두 컴포넌트의 동작 확인 (App.js)
사용 중인 React 프로젝트의 src/App.js에서 위의 두 컴포넌트를 불러옵니다. fetch 발췌나 구독 발췌를 App에 추가하는 단계는 아닙니다. 화면과 콘솔을 함께 보고 아래 조건별로 무엇이 정리되는지 확인해 주세요.
import React, { useState } from 'react';
import './App.css';
import UserFetcher from './components/UserFetcher';
import SearchComponent from './components/SearchComponent';
function App() {
const [showUserFetcher, setShowUserFetcher] = useState(true);
return (
<div className="App">
<h1>useEffect 심화 학습</h1>
<button onClick={() => setShowUserFetcher(!showUserFetcher)} style={{ margin: '10px', padding: '10px 20px' }}>
UserFetcher {showUserFetcher ? '숨기기' : '보이기'}
</button>
{showUserFetcher && <UserFetcher />} {/* 조건부 렌더링으로 마운트/언마운트 테스트 */}
<hr />
<SearchComponent />
</div>
);
}
export default App;일반 마운트와 개발 환경의 추가 점검을 구분하며 다음 순서로 확인해 봅니다. 타이머의 시간은 예약한 지연이며, 실제 콜백은 브라우저 작업 상황에 따라 더 늦게 실행될 수 있습니다.
| 조건과 행동 | 소스에서 예상할 관찰 | 다를 때 먼저 확인할 곳 |
|---|---|---|
| UserFetcher를 계속 표시 | setup 로그 뒤 1.5초 지연을 거쳐 고정 사용자 표시 | 로딩 상태와 fetchedUser의 고정 값 |
| UserFetcher를 1.5초 전에 숨기기 | cleanup 로그와 함께 화면에서 제거됨. 예약한 Promise 흐름은 계속되며 취소되지 않음 | cleanup에 로그 외 타이머 해제나 늦은 결과 guard가 있는지 |
| 2글자 이상 검색어를 넣고, 500ms 전에 다른 2글자 이상 검색어로 바꾸기 | 아직 대기 중인 이전 타이머 정리 뒤 새 타이머 설정. 마지막 검색어의 고정 결과 표시 | clearTimeout(timer)와 콘솔의 setup/정리/결과 로그 |
| 검색어를 2글자 미만으로 줄이기 | 이전 대기 타이머를 정리하고 결과·로딩 상태를 비움. 새 타이머는 만들지 않음 | 길이 검사 뒤의 조기 return |
| 개발 Strict Mode의 추가 점검이 적용됨 | 추가 setup→cleanup→setup 로그가 보일 수 있음. UserFetcher의 지연 작업은 cleanup에서 취소되지 않음 | 프로젝트의 Strict Mode 적용 위치와 각 cleanup의 실제 코드 |
이전 타이머 콜백이 이미 실행된 뒤라면 clearTimeout으로 그 결과를 되돌릴 수 없습니다. 또 UserFetcher의 고정 값은 같아서 화면만으로 중복 예약을 구별하기 어렵습니다. cleanup 로그가 보였다는 사실과 실제로 자원을 정리했다는 사실을 구분해 주세요.
대기 타이머를 시간표로 추적하기
SearchComponent의 길이 검사와 타이머를 함께 읽어 봅니다. 아래 시간은 각 Effect의 setup이 실행된 시점으로 가정하며, 500ms가 되기 전에 정리한 타이머의 콜백은 아직 실행되지 않았다고 가정합니다. 실제 브라우저의 입력·렌더·Effect가 정확히 같은 시각에 실행된다는 뜻은 아닙니다.
처음 setup 시점을 0ms로 두고 검색어를 '자바'로 입력합니다. 200ms에는 '타입'으로, 600ms에는 '타'로 바꿔 각각 다음 setup을 실행한다고 할 때, 예약 시각과 해제 대상, 마지막 결과·로딩 상태를 직접 적어 보세요. 외부 API 호출이 아니라 이 예제의 대기 타이머를 판단하는 문제입니다.
타이머별 예약과 해제
첫 타이머는 500ms 이후 실행되도록 예약되지만, 200ms의 cleanup에서 해제됩니다. 두 번째 타이머는 700ms 이후 실행되도록 예약되지만, 600ms의 cleanup에서 해제됩니다. 마지막 setup은 한 글자이므로 결과를 빈 배열로, 로딩을 false로 바꾸고 새 타이머를 만들지 않습니다. 가정한 시간표 안에서는 두 콜백 모두 실행되지 않습니다. 이미 실행된 결과는 타이머를 해제하는 것으로 되돌릴 수 없습니다.
의존성 비교는 각 항목을 이전 렌더의 항목과 Object.is로 대조합니다. 같은 문자열이면 이 값 때문에 다시 동기화하지 않지만, 매 렌더 새로 만든 객체는 내용이 같아도 참조가 다릅니다. 실행 횟수를 줄이려고 Effect가 실제 읽는 반응형 값을 배열에서 빼면 오래된 값을 사용할 수 있으므로, 공식 의존성 안내처럼 먼저 동기화 대상을 분명히 합니다.
위 그림은 구독의 정리 순서를 설명합니다. UserFetcher는 로그만 남기고, SearchComponent는 아직 대기 중인 타이머를 해제합니다. fetch 발췌는 실제 endpoint와 결과 처리 문맥을 갖춘 경우의 중단 방법입니다. 세 예제의 정리 범위를 같은 요청 취소로 읽지 않습니다.
useEffect의 의존성 배열은 단순히 실행 횟수를 줄이는 장치가 아니라, effect가 어떤 값과 동기화되는지를 드러내는 계약입니다.
useEffect 심화: 라이프사이클과 클린업은 여기까지입니다.
이 장에서는 useEffect 훅의 의존성 배열(deps 배열)의 다양한 활용 패턴과 그 의미를 명확히 하고, 클린업 함수의 중요성 및 메모리 누수 방지를 위한 활용 사례들을 상세하게 다루었습니다.
다음 코드를 읽을 때도 setup이 시작한 작업과 cleanup이 실제로 끝내는 작업을 짝지어 봅니다. 로그만 남기는 함수와 자원을 해제하는 함수는 결과가 다릅니다.
다음 절에서는 useContext를 사용해 컴포넌트 트리에서 전역 데이터를 공유하는 방법을 다루겠습니다.