JavaScript 클로저와 React Hooks의 동작 원리 이해하기
프론트엔드 개발을 하다 보면 클로저(Closure)라는 개념을 자주 마주치게 됩니다. 특히 React Hooks를 사용할 때 클로저는 핵심적인 역할을 합니다. 하지만 많은 개발자들이 클로저의 개념을 어렵게 느끼고, React Hooks와의 관계를 명확히 이해하지 못하고 있습니다.
이 글에서는 클로저의 기본 개념부터 시작해서, React Hooks가 어떻게 클로저를 활용하는지 상세히 알아보겠습니다.
목차
클로저란 무엇인가?
클로저는 함수가 자신이 생성될 당시의 환경을 기억하는 현상을 말합니다. 조금 더 전문적으로 말하면, 함수와 그 함수가 선언된 렉시컬 환경(Lexical Environment)의 조합입니다.
쉽게 이해하기 위해 일상생활의 예시를 들어보겠습니다:
여러분이 가방을 들고 있다고 상상해보세요. 이 가방 안에는 여러분의 물건들이 있습니다. 가방을 들고 다른 장소로 이동해도, 가방 안의 물건들은 그대로 유지됩니다. 이것이 바로 클로저와 비슷합니다. 함수가 '가방'이고, 함수가 접근할 수 있는 변수들이 '가방 안의 물건들'인 셈입니다.
간단한 예제로 살펴보겠습니다:
function createCounter() {
let count = 0; // 프라이빗 변수
return function() {
return ++count;
};
}
const counter = createCounter();
console.log(counter()); // 1
console.log(counter()); // 2
이 예제에서 createCounter
가 반환하는 함수는 자신이 생성될 때의 환경(count 변수가 있는 환경)을 기억합니다. 이것이 바로 클로저입니다.
클로저의 동작 원리
클로저가 동작하는 방식을 좀 더 자세히 살펴보겠습니다.
1. 렉시컬 스코프 (Lexical Scope)
JavaScript는 렉시컬 스코프을 사용합니다. 이는 함수가 자신이 작성된 위치의 스코프를 기억한다는 의미입니다.
function outer() {
const message = 'Hello'; // 외부 함수의 변수
function inner() {
console.log(message); // 외부 함수의 변수에 접근
}
return inner;
}
const sayHello = outer();
sayHello(); // "Hello" 출력
여기서 중요한 점은:
- inner 함수는 자신이 생성된 환경(outer 함수의 스코프)을 기억합니다.
- outer 함수가 실행을 마쳐도, inner 함수는 여전히 message 변수에 접근할 수 있습니다.
2. 스코프 체인 (Scope Chain)
클로저는 스코프 체인을 통해 동작합니다:
- 함수는 자신의 지역 스코프를 먼저 살펴봅니다.
- 찾는 변수가 없으면 외부 스코프를 찾아봅니다.
- 이 과정을 전역 스코프까지 반복합니다.
3. 가비지 컬렉션 (Garbage Collection)
일반적으로 함수 실행이 끝나면 해당 함수의 지역 변수들은 가비지 컬렉션의 대상이 됩니다. 하지만 클로저가 형성되면:
- 클로저가 참조하는 변수들은 메모리에서 해제되지 않습니다.
- 이 변수들은 클로저가 존재하는 한 계속 유지됩니다.
클로저가 중요한 이유
클로저는 다음과 같은 중요한 기능을 제공합니다:
1. 데이터 프라이버시
클로저를 통해 private 변수와 비슷한 효과를 얻을 수 있습니다:
function createUser(name) {
// private 변수
let privateName = name;
return {
getName() {
return privateName;
},
setName(newName) {
privateName = newName;
}
};
}
2. 상태 유지
클로저는 함수가 자신의 상태를 유지할 수 있게 해줍니다:
function createLogger(prefix) {
let count = 0; // 로그 카운트 유지
return function(message) {
count++;
console.log(`${prefix} (${count}): ${message}`);
};
}
3. 모듈 패턴 구현
클로저를 통해 모듈화된 코드를 작성할 수 있습니다:
const userModule = (function() {
let users = []; // private 데이터
return {
addUser(user) {
users.push(user);
},
getUsers() {
return [...users]; // 복사본 반환
}
};
})();
React에서의 클로저
React에서 클로저는 특히 중요한 역할을 합니다. React의 핵심 기능들이 클로저를 기반으로 구현되어 있기 때문입니다.
1. 컴포넌트의 상태 관리
React 컴포넌트는 클로저를 통해 자신의 props와 state를 기억합니다:
function Counter() {
const [count, setCount] = React.useState(0);
// 이벤트 핸들러는 클로저를 형성하여 count에 접근
const handleClick = () => {
setCount(count + 1);
};
return <button onClick={handleClick}>{count}</button>;
}
React Hooks와 클로저의 관계
React Hooks는 클로저의 원리를 활용하여 구현되어 있습니다. 각각의 Hook이 어떻게 클로저를 활용하는지 살펴보겠습니다.
1. useState와 클로저
useState Hook은 클로저를 통해 컴포넌트의 상태를 관리합니다:
- React는 내부적으로 각 컴포넌트의 상태를 배열로 관리합니다.
- 클로저를 통해 이 배열에 접근하고 업데이트합니다.
- 각 렌더링마다 새로운 클로저가 생성되지만, 상태 값은 React에 의해 보존됩니다.
2. useEffect와 클로저
useEffect는 클로저를 통해 의존성 배열의 값들을 기억합니다:
function SearchComponent({ query }) {
useEffect(() => {
// 이 함수는 클로저를 형성하여 query 값을 기억
const search = async () => {
const results = await searchAPI(query);
// ...
};
search();
}, [query]); // query가 변경될 때마다 새로운 클로저 생성
}
여기서 주의할 점:
- 이펙트 함수는 렌더링 시점의 props와 state를 캡처합니다.
- 의존성 배열이 변경되면 새로운 클로저가 생성됩니다.
- cleanup 함수도 자신만의 클로저를 형성합니다.
3. useCallback과 클로저
useCallback은 클로저를 활용하여 함수를 메모이제이션합니다:
- 메모이제이션된 함수는 자신이 생성된 시점의 props와 state를 기억합니다.
- 의존성 배열이 변경되면 새로운 클로저가 생성됩니다.
실제 사용 사례와 주의점
1. Stale Closure 문제
가장 흔한 실수 중 하나는 'stale closure' 문제입니다:
function Counter() {
const [count, setCount] = React.useState(0);
// ❌ 문제가 있는 코드
React.useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1); // 오래된 count 값을 참조
}, 1000);
return () => clearInterval(timer);
}, []);
// ✅ 올바른 해결책
React.useEffect(() => {
const timer = setInterval(() => {
setCount(prev => prev + 1); // 함수형 업데이트 사용
}, 1000);
return () => clearInterval(timer);
}, []);
}
2. 성능 최적화
클로저를 활용한 성능 최적화:
function SearchInput({ onSearch }) {
// 디바운스된 검색 함수
const debouncedSearch = React.useCallback(
debounce((term) => {
onSearch(term);
}, 500),
[onSearch]
);
return (
<input
onChange={e => debouncedSearch(e.target.value)}
/>
);
}
마무리
클로저는 JavaScript와 React의 핵심 개념입니다. 특히 React Hooks는 클로저의 원리를 기반으로 동작하며, 이를 이해하는 것은 효과적인 React 애플리케이션 개발에 매우 중요합니다.
클로저를 잘 활용하면:
- 더 깔끔하고 모듈화된 코드를 작성할 수 있습니다.
- 상태 관리를 효과적으로 할 수 있습니다.
- React Hooks의 동작 원리를 더 잘 이해하고 활용할 수 있습니다.
하지만 주의할 점도 있습니다:
- 불필요한 클로저는 메모리 누수를 일으킬 수 있습니다.
- Stale closure 문제에 주의해야 합니다.
- 의존성 배열 관리에 신경 써야 합니다.
이러한 개념들을 잘 이해하고 적절히 활용한다면, 더 효과적이고 버그 없는 React 애플리케이션을 개발할 수 있을 것입니다.
참고 자료
- React 공식 문서 - Hooks
- MDN - 클로저
- JavaScript Info - 클로저
- React Working Group - Hooks FAQ
- Dan Abramov - React의 클로저
https://covelope.tistory.com/entry/guide-to-react-javascript-memory-leak-analysis-and-optimization
React & JavaScript 메모리 누수 분석과 해결 방법 - 최적화 노하우
안녕하세요! 오늘은 React와 JavaScript 애플리케이션에서 발생하는 메모리 누수 문제를 깊이 있게 다뤄보겠습니다. 실제 프로젝트에서 마주칠 수 있는 다양한 시나리오와 해결 방법을 함께 알아보
covelope.tistory.com
https://covelope.tistory.com/entry/Javascript-core-concepts-prototype-execution-context-this
JavaScript 핵심 개념 이해하기 : 프로토타입, 실행 컨텍스트, this 바인딩 정리
JavaScript를 배우면서 가장 어려운 개념을 꼽으라면 단연 프로토타입, this, 그리고 실행 컨텍스트라고 말할 수 있을 거 같습니다. 우연히 좋은 내용에 글을 보고 이해한 내용을 정리하도록 하겠습
covelope.tistory.com
https://covelope.tistory.com/entry/React-useState-implementation
리액트(React) - useState 내부 구현과 동작 원리(feat : 클로저)
리액트에서 함수형 컴포넌트 이전 클래스형 컴포넌트 사용시에는 상태를 지역변수 state에 정의하고 상태를 변경할 메소드 안에 setState 메소드를 넣어서 상태를 변경 했다.그럼 왜 state변수를 직
covelope.tistory.com
'JavaScript' 카테고리의 다른 글
JavaScript 배열 기초 정리: 핵심 개념부터 유용한 메서드까지 (0) | 2025.03.06 |
---|---|
JavaScript 핵심 개념 이해하기 : 프로토타입, 실행 컨텍스트, this 바인딩 정리 (0) | 2025.02.13 |
[JavaScript] - 호출 스택, 콜백 큐, 이벤트 루프: 실행 프로세스 이해하기 (1) | 2023.10.06 |
자바스크립트(javascript) - 엔진,런타임,힙,스택,이벤트루프,프로세스 (0) | 2023.09.25 |
자바스크립트 비동기(Asynchronous) 과정 .feat(AST) (0) | 2023.05.08 |