useState의 내부동작에는 자바스크립트의 클로저개념이 사용된다.
클로저가 사용되는 코드를 알아보고 클로저의 개념도 다시 상기시켜보자.
클로저는 반환된 내부함수가 자신이 선언되었을 때의 렉시컬환경인 스코프를 기억하여 자신이 선언되었을 때의 환경(스코프)밖에서 호출이 되어도 그 환경에 접근할 수 있는 함수를 말한다.
function outerFunc() {
var x = 10;
var innerFunc = function() { console.log(x); };
return innerFunc;
}
/**
* 함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
* 그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
*/
var inner = outerFunc();
inner(); // 10
함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 지역변수를 참조할 수 있는데 이러한 중첩함수를 클로저라고 부른다.
클로저는 상태를 안전하게 변경하고 은닉화시키기 위해 사용한다.
상태가 의도치 않게 변경되지 않고 특정 함수에게만 상태 변경을 허용하기 위해서이다.
클로저에 의해 참조되는 외부함수의 변수(outerFunc) 함수의 변수 x를 자유변수(Free variable)
라고 부르는데 클로저라는 이름은 자유변수에 함수가 닫혀있다(closed)라는 의미로 의역하면 자유변수에 엮여있는 함수라는 뜻이다.
클로저는 자신이 생성될 때의 환경을 기억한다.
클로저가 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다.
useState도 값을 변경할 때 setState를 통해 상태를 변경하고, 함수가 실행되었을 때 이전 상태를 기반으로 상태가 변경되며 항상 최신의 state를 유지한다.
실제로 컴포넌트에서 이전 상태와 현재 상태의 변경을 감지하기 위해서 함수가 실행되었을 때 이전 상태에 대한 정보를 가지고 있어야 한다. 그리고 이 과정에서 클로저를 사용한다.
function useState(initialState) {
var dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
useState의 내부 코드는 위와 같이 정의되어있다.
dispatcher에는 resolveDispatcher함수의 결과가 할당되고, dispatcher의useState메서드에 초기값을 전달하고 반환된 값을 리턴한다.
function resolveDispatcher() {
var dispatcher = ReactCurrentDispatcher.current;
return dispatcher;
}
그리고 resolveDispatcher는 ReactCurrentDispatcher라는 객체의 current프로퍼티를 반환하는데
여기서 ReactCurrentDispatcher는 전역에 설정되어있는 객체이다.
때문에 useState가 반환한 상태의 배열은 전역객체로부터 오고 hook을 호출하면 클로저 동작에 의해 컴포넌트 밖에 있는 외부 스코프에 존재하는 state값에 접근할 수가 있다. 컴포넌트에서 값의 변경이 있으면 외부의 값이 변경되고 컴포넌트 내부에서 사용된다.
참고
https://velog.io/@ggong/useState-Hook과-클로저
https://www.kyoung-jnn.com/posts/react-useState
https://www.rinae.dev/posts/getting-closure-on-react-hooks-summary