클로저를 공부하면서 정보은닉, 커링을 이용한 함수 지연 실행 등의 활용법이 있는 것을 알고 있었지만, 실 사례에서 redux의 미들웨어에 사용되는 것 외에는 와닿는 부분이 없었다. 그러던 중 React Hook이 클로저를 이용해 구현되었다는 것을 보고, 공부해보려 한다.

클로저란?

클로저가 무엇인지 리마인드 해보자.

클로저는 반환된 내부함수가 자신의 렉시컬 스코프를 기억하여 외부에서 호출되어도 그 스코프에 접근할 수 있는 것을 말한다.

const outerFunc = () => {
		let x = 10; // 자유 변수

		// 클로저
		const innerFunc = (y) => {
				x = x + y;
				console.log(x);
		}

		return innerFunc;
}

const addFunc = outerFunc();
addFunc(5); // 15
addFunc(10); // 25

클로저가 왜 쓰일까?

함수형 컴포넌트는 렌더링이 필요할 때 마다 함수를 다시 호출한다.

따라서, 상태관리를 위해 함수가 다시 호출되더라도 이전의 상태를 기억하고 있어야 하는데, 이 때 클로저가 사용된다.

useState 개념 구현

다른 분이 작성한 useState 구현 코드를 참조하여 하나씩 이해해보고자 한다.

const React = (function () {
	// Hook이 여러개 일 경우, 각 상태를 관리하기 위해 배열을 만든다.
	// (Hook이 조건문 내에서 사용되면 안되는 이유)
  let states = []; 
  // 해당 Hook의 State를 찾아가기 위한 인덱스
  let idx = 0;

	// React의 호출이 끝나더라도, 내부함수인 useState에서
	// states를 참조하고 있기 때문에 외부에서 접근할 수 있다 (클로저)
  function useState(initialVal) {
    const state = states[idx] || initialVal;

		// setState가 호출되는 시점에 증가된 인덱스를 사용하지 않도록 방지하기 위해
		// setState가 참조하는 인덱스의 스코프를 바꿈.
    const _idx = idx; 
    const setState = (newVal) => {
      states[_idx] = newVal; 
    };

		// useState가 호출될 때마다 인덱스를 증가시켜 해당하는 state를
    // states 배열에서 꺼내올 수 있도록 한다.
    idx++;
    return [state, setState];
  }

  function render(Component) {
		// render가 호출되면 useState를 호출하기 때문에
		// 인덱스가 계속 증가하는걸 방지하기 위해 0으로 초기화 한다.
    idx = 0; 

		// render가 호출되면 Component()를 호출한다.
		// (렌더링이 필요할 때 마다 함수를 다시 호출한다)
    const C = Component();
    C.render();
    return C;
  }

  return { useState, render };
})();

function Component() {
  const [count, setCount] = React.useState(1);
  const [text, setText] = React.useState("apple");

  return {
    render: () => console.log({ count, text }),
    click: () => setCount(count + 1),
    type: (word) => setText(word),
  };
}

var App = React.render(Component); // { count: 1, text: 'apple' }
App.click();
var App = React.render(Component); // { count: 2, text: 'apple' } 
App.click();
var App = React.render(Component); // { count: 3, text: 'apple' } 
App.type("orange");
var App = React.render(Component); // { count: 3, text: 'orange' } 
App.type("peach");
var App = React.render(Component); // { count: 3, text: 'peach' } 

Hook이 조건문 내에서 사용되면 안되는 이유

위 코드에서 Hook에 대한 state들을 배열로 관리하는 것을 볼 수 있는데, 이게 Hook이 조건문 내에서 사용되면 안되는 이유이다.

useState가 호출될 때 마다, 배열에 state가 순서대로 저장되기 때문에, 조건문에 따라 Hook의 실행 순서가 바뀐다면, 다른 인덱스의 state를 참조하는 상황이 발생한다.