본문 바로가기
Modern React Deep Dive/01장 리액트 개발을 위해 꼭 알아야 할 자바스크립트

1.4.3 클로저의 활용

by Whiimsy 2024. 4. 9.

1.4.3 클로저의 활용

클로저의 정의인 "함수와 함수가 선언된 어휘적 환경의 조합"이 무엇인지 살펴보았음. 자바스크립트는 함수 레벨 스코프를 가지고 있으므로, 이렇게 선언된 함수 레벨 스코프를 활용해 어떤 작업을 할 수 있다는 개념이 바로 클로저.

function outerFunction() {
  var x = 'hello'
  function innerFunction() {
    console.log(x)
  }

  return innerFunction
}

const innerFunction = outerFunction()
innerFunction() // "hello"

 

자바스크립트가 함수 레벨 스코프를 가지고 있고 이러한 스코프는 동적으로 결정되기 때문에 위 예제에서 "hello"가 출력됨. 위 예제에서 outerFunctioninnerFunction을 반환하며 실행이 종료됨. 여기에서 반환한 함수에는 x라는 변수가 존재하지 않지만, 해당 함수가 선언된 어휘적 환경, 즉 outerFunction에는 x라는 변수가 존재하며 접근할 수도 있음. 따라서 같은 환경에서 선언되고 반환된 innerFunction에서는 x라는 변수가 존재하던 환경을 기억하기 때문에 정상적으로 "hello"를 출력할 수 있는 것.

클로저의 활용

전역 스코프는 어디서든 원하는 값을 꺼내올 수 있다는 장점이 있지만, 반대로 이야기하면 누구든 접근할 수 있고 수정할 수 있다는 뜻이기도 함.

var counter = 0

function handleClick() {
  counter++
}

 

counter 변수는 큰 문제를 가지고 있음. 첫째, 전역 레벨에 선언돼 있어 누구나 수정할 수 있음. window.counter를 활용하면 쉽게 해당 변수에 접근할 수 있음. 만약 리액트의 useState 변수가 전역 레벨에 저장돼 있으면 어떻게 될까? 누구나 리액트 애플리케이션을 쉽게 망가뜨릴 것임... 따라서 리액트가 관리하는 내부 상태 값은 리액트가 별도로 관리하는 클로저 내부에서만 접근할 수 있음.

function Counter() {
  var counter = 0

  return {
    increase: function () {
      return ++counter
    },
    decrese: function () {
      return --counter
    },
    counter: function () {
      console.log('couter에 접근!')
      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

 

https://ui.dev/javascript-visualizer에서 ES5 자바스크립트 코드를 입력해 실행하면 자바스크립트 코드가 어떤 식으로 실행되고 있는지, 어떤 클로저가 현재 존재하는 지 등도 확인할 수 있음.

 

JavaScript Visualizer

 

ui.dev

image

위와 같이 코드를 변경했을 때 얻을 수 있는 이점에는 여러가지가 있음. 먼저 counter 변수를 직접적으로 노출하지 않음으로써 사용자가 직접 수정하는 것을 막았음은 물론, 접근하는 경우를 제한해 로그를 남기는 등의 부차적인 작업도 수행할 수 있음. 또한 counter 변수의 업데이트를 increasedecrease로 제한해 무분별하게 변경되는 것을 막았음. 이처럼 클로저를 활용하면 전역 스코프의 사용을 막고, 개발자가 원하는 정보만 개발자가 원하는 방향으로 노출시킬 수 있다는 장점이 있음.

위 예제 코드를 살펴보면, 리액트에서는 클로저를 어떻게 사용하고 있을지 어렴풋하게 짐작이 가능함. useState의 변수를 저장해두고, useState의 변수 접근 및 수정 또한 클로저 내부에서 확인이 가능해 값이 변하면 렌더링 함수를 호출하는 등의 작업이 이루어질 것.

리액트에서의 클로저

리액트 함수형 컴포넌트의 훅에서 클로저는 어떻게 사용될까? 클로저의 원리를 사용하고 있는 대표적인 것 중 하나가 바로 useState임.

function Component() {
  const [state, setState] = useState()

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

  // ...
}

 

useState 함수의 호출은 Component 내부 첫 줄에서 종료됐는데, setStateuseState 내부의 최신 값을 어떻게 계속해서 확인할 수 있을까? 그것은 바로 클로저가 useState 내부에서 활용됐기 때문. 외부 함수(useState)가 반환한 내부 함수(setState)는 외부 함수(useState)의 호출이 끝났음에도 자신이 선언된 외부 함수가 선언된 환경(state가 저장돼 있는 어딘가)을 기억하기 때문에 계속해서 state 값을 사용할 수 있는 것.