리액트를 하다보면 클로저라는 단어가 늘 언급이 한번씩 나온다.
모던리액트 딥다이트 책에서도 이 부분에서는 어렵다고 설명을 해주는데
사실 1년전 처음 이걸 배웠을 때만 해도 이해가 가지 않았고 오랜만에
스터디를 통해 다시금 클로저를 접했지만서도 이 부분은 이해가 필요해서 다시금 짚어가고 싶어서 필기하려 한다.
클로저란?
상태를 안전하게 변경하고 유지하기 위해 사용한다.
다시말해, 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.
변수 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있다.
외부 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에서 부수효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다.
아래는 함수형 프로그래밍에서 클로저를 활용하는 간단한 예제다.
// 함수를 인수로 전달받고 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
// 카운트 상태를 유지하기 위한 자유 변수
let counter = 0;
// 클로저를 반환
return function () {
// 인수로 전달받은 보조 함수에 상태 변경을 위임한다.
counter = aux(counter);
return counter;
};
}
// 보조 함수
function increase(n) {
return ++n;
}
// 보조 함수
function decrease(n) {
return --n;
}
// 함수로 함수를 생성한다.
// makeCounter 함수는 보조 함수를 인수로 전달받아 함수를 반환한다.
const increaser = makeCounter(increase);
console.log(increaser()); // 1
console.log(increaser()); // 2
// increaser 함수와는 별개의 독립된 렉시컬 환경을 갖기 때문에 카운터 상태가 연동하지 않는다.
const decreaser = makeCounter(decrease);
console.log(decreaser()); // -1
console.log(decreaser()); // -2
위 예제에서는 전역 변수 iuncreaser와 decreaser에 할당된 함수는 각각 자신만의 독립된 렉시컬 환경을 갖기 때문에 카운트를 유지하기 위한 자유 변수 counter를 공유하지 않아 카운터의 증감이 연동되지 않는다. 따라서 독립된 카운터가 아니라 연동하여 증감이 가능한 카운터를 만들려면 렉시컬 환경을 공유하는 클로저를 만들어야 한다.
이를 위해서는 makeCounter 함수를 두 번 호출하지 말아야 한다.
// 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
const counter = (function () {
// 카운트 상태를 유지하기 위한 자유 변수
let counter = 0;
// 함수를 인수로 전달받는 클로저를 반환
return function (aux) {
counter = aux(counter);
return counter;
};
}());
// 보조 함수
function increase(n) {
return ++n;
}
// 보조 함수
function decrease(n) {
return --n;
}
// 보조 함수를 전달하여 호출
console.log(counter(increase)) //1
console.log(counter(increase)) //2
// 보조 함수를 전달하여 호출
console.log(counter(decrease)) //1
console.log(counter(decrease)) //0
선뜻 이걸 이해하고 나서 나중에 알았던 사실인데
React의 useState도 클로저를 이용해서 구현되었다는 것이라는 거다.
글을 마치며 다소 어렵고 낯선 개념인 클로저에 대해서 쉽게 풀어보고 싶어서 많은 예시를 들다보니 내용이 꽤나 길어진 것 같은데 계속적인 반복연습을 통해서 학습을 해야한다고 느낀다.