모던 리액트 Deep Dive 스터디

리액트 개발을 위해 꼭 알아야 할 자바스크립트 - 클로저

려낭 2024. 11. 6. 00:49

1.4.1 클로저의 정의

 

MDN 의 클로저 정의 : 함수와 함수가 선언된 어휘적 환경의 조합

                                                                     ㄴ  변수가 코드 내부에서 어디에 선언됐는지

 

클로저는 어휘적 환경을 조합해 코딩하는 기법이다.

 

1.4.2 변수의 유효 범위, 스코프

 

스코프는 변수의 유효범위이다.

 

전역 스코프

  • 전역 레벨에 선언하는 것이다.
  • 이 스코프에서 변수를 선언하면 어디서든 호출할 수 있다.
var global = 'global scope'
function hello() {
  console.log(global)
}

console.log(global) // global scope
hello() // global scope
console.log(global === window.global) // true

 

전역 스코프에 변수를 선언하면 코드 전체 어디에서든 그 변수를 쓸 수 있게 되고,

브라우저에서는 window 객체가 전역 변수들을 관리하는 역할을 한다.

 

함수 스코프

  • 자바스크립트는 기본적으로 함수 레벨 스코프를 따른다.
  • {} 블록이 스코프 범위를 결정하지 않는다.
if (true) {
  var global = 'global scope'
}

console.log(global) //'global scope'
console.log(global === window.global) //true

 

var global은 분명 {} 내부에 선언돼 있는데, {} 밖에서도 접근 가능하다. 

>> 자바스크립트는 함수 레벨 스코프를 가지고 있기 때문이다.

 

 

1.4.3 클로저의 활용

 

전역 스코프는 어디서든 원하는 값을 꺼내올 수 있다는 장점이 있지만,

반대로는 누구든 접근할 수 있고 수정할 수 있다는 뜻도 된다.

var counter = 0

function handleClick() {
  counter++
}

 

위 코드는 전역 레벨에 선언되어 있어 누구나 수정할 수 있다.

 

자바스크립트를 조금만 아는 사람이라면 누구나 리액트 애플리케이션을 쉽게 망가뜨릴 수 있을 것이다.

그래서 리액트가 관리하는 내부 상태 값은 리액트가 별도로 관리하는 클로저 내부에서만 접근할 수 있다.

 

function Counter() {
  var counter = 0
  return {
   increase: function () {
    return ++counter
   },
   decrease: function () {
    return --counter
   },
   counter: function () {
    console.log('counter에 접근!')
    return counter
   },
  }
}

var c = Counter()
console.log(c.increase()) // 1
console.log(c.increase()) // 2
console.log(c.increase()) // 3
console.log(c.decrease()) // 2
console.log(c.counter()) // 2

 

클로저를 활용하면 전역 스코프의 사용을 막고, 개발자가 원하는 정보만 개발자가 원하는 방향으로 노출시킬 수 있다.

 

 

리액트에서의 클로저

 

리액트 함수 컴포넌트 훅에서 클로저는 어떻게 사용될까?

대표적인 예로 useState를 들어보자.

function Component() {
  const [state, setState] = useState()
  
  function handleClick() {
  //useState호출은 위에서 끝났지만
  //setState는 계속 내부의 최신값(prev)을 알고 있다.
  //이는 클로저를 활용했기 때문에 가능하다.
  setState((prev) => prev + 1)
  }
}

 

외부 함수(useState)가 반환한 내부 함수(setState)는 외부함수(useState)의 호출이 끝났음에도 자신이 선언된 외부 함수가 선언된 환경(state가 저장돼 있는 어딘가)을 기억하기 때문에 계속해서 state 값을 사용할 수 있다.

 

1.4.4 주의할 점
for (var i = 0; i < 5; i++) {
  setTimeout(function () {
   console.log(i)
  }, i * 1000)
}

위의 코드의 의도는 0부터 시작해 1초 간격으로 0,1,2,3,4를 차례대로 출력하는 것이다.

그러나 실제로 실행하면 0,1,2,3,4 초 뒤에 5만 출력된다.

 

왜 ?

i가 전역 변수로 작동하기 때문이다.

 

for 문을 다 순회한 이후, 태스크 큐에 있는 setTimeout을 실행하려고 했을 때, 이미 전역 레벨에 있는 i는 5로 업데이트 되어 있다.

 

올바르게 수정하는 방법 1

함수 레벨 스코프가 아닌 블록 레벨 스코프를 갖는 let으로 수정하는 것.

for (let i = 0; i < 5; i++) {
  setTimeout(function () {
   console.log(i)
  }, i * 1000)
}

let은 블록 레벨 스코프를 가지기 때문에 let i가 for 문을 순회하면서 각각의 스코프를 갖게 된다.

 

 

클로저의 기본 개념인 '함수와 함수가 선언된 어휘적 환경의 조합'을 주의 깊게 살펴봐야 클로저를 제대로 활용할 수 있다.

 

주의할 점

  • 클로저를 사용하는 데는 비용이 든다.

-생성될 때 마다 선언적 환경을 기억해야 하므로 추가로 비용이 발생한다

 

외부 함수를 기억하고 이를 내부 함수에서 가져다 쓰는 메커니즘은 성능에 영향을 미친다.