React/프론트엔트 개념 정리

클로저(Closure)란? – React에서 stale 값이 생기는 이유

연신내고독한늑대 2025. 10. 17. 11:44

■ 클로저(Closure)란?

Closure

- 사전적 의미: “닫힘, 폐쇄, 종결”
- 프로그래밍에서는 “함수가 선언될 당시의 환경을 닫아서 보존한다”는 뜻. 

즉, 함수가 선언될 당시의 외부 스코프(환경, lexical environment)를 기억하고, 그 함수가 외부 스코프 밖에서 실행되더라도 그 변수에 접근할 수 있게 해주는 기능.

// 예시 코드
function outer() {
  let count = 0; // 외부 스코프 변수

  function inner() {
    count++;
    return count;
  }

  return inner;
}

const counter = outer();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3

작동 원리
1. outer()가 실행되면 내부에 count 변수가 생성되고, inner 함수가 만들어짐
2. outer()는 끝났지만, 반환된 inner는 자신이 선언된 스코프(outer의 환경) 을 기억함.
3. 그래서 count는 계속 살아있고, inner()를 호출할 때마다 이전 값을 기억하고 증가시킴.

>>> 즉, 함수가 “태어날 당시의 기억(환경)”을 간직하고 있는 것.

 

■ 스코프(Scope)란?

변수가 접근 가능한 범위를 의미

자바스크립트는 렉시컬 스코프(Lexical Scope) 를 사용. 즉, 함수가 선언된 위치에 따라 스코프가 결정.

 

클로저가 "외부 스코프 변수"를 기억한다는 건, 그냥 변수를 복사해오는 게 아니라 외부 스코프 자체를 통째로 기억하고 있다는 뜻.

그래서 함수가 종료된 뒤에도 외부 변수에 접근할 수 있는 것.

 

■ React에서 클로저가 문제가 되는 이유

React는 함수형 컴포넌트 기반으로 작동하므로 컴포넌트가 렌더링될 때마다 함수 전체가 다시 실행됨.
그런데 클로저는 “그 당시 렌더링 시점의 상태(state)”를 캡쳐해서 기억해버려서 이전 렌더링의 상태를 참조(stale 값 참조) 하는 문제가 생김. 그래서 useEffect이나 이벤트핸들링 사용할 때 이 전의 값을 참조해서 stale값이 유지되는 현상이 발생함.

// 문제사항 예시
import { useState, useEffect } from "react";

export default function Counter() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    const id = setInterval(() => {
      console.log("count:", count); // 👈 항상 0만 찍힘
      setCount(count + 1);
    }, 1000);

    return () => clearInterval(id);
  }, []); // 👈 빈 배열
  return <h1>{count}</h1>;
}

// 문제 발생 이유
1. useEffect는 처음 렌더링 시 딱 한 번 실행([] 의존성 때문)
2. 그때의 count 값은 0.
3. 클로저가 당시 count=0을 기억함.
4. setInterval 안의 함수는 계속 그 “옛날 count”를 사용.
5. 그래서 콘솔엔 계속 count: 0만 찍히고, 값이 안 바뀜.

■■ 해결 방법

1. 콜백 함수형 setState 사용

useEffect(() => {
  const id = setInterval(() => {
    setCount(prev => prev + 1); // ✅ 항상 최신 prev 값 참조
  }, 1000);

  return () => clearInterval(id);
}, []); // interval 한 번만 설정

- setCount(prev => prev + 1)는 React가 가장 최신 상태(prev) 를 전달
- 따라서 stale 값 문제 없이 안정적으로 동작.

 

2. 의존성 배열에 상태값 추가

useEffect(() => {
  const id = setInterval(() => {
    console.log("count:", count);
    setCount(count + 1);
  }, 1000);

  return () => clearInterval(id);
}, [count]); // ✅ count가 바뀔 때마다 최신 값 반영

 

 

>>> 결론

React는 함수형 컴포넌트가 랜더링 될 때, 내부의 클로저가 랜더링 될 때의 값을 참조하는 문제가 있음. 그래서 useEffect이나 이벤트핸들링 사용할 때 이 전의 값을 참조해서 stale값이 유지되는 현상이 발생함.
∴ 해결방안: 의존형 배열에 값을 넣거나 콜백함수로 만들면 됨.