자바스크립트를 하면서 클로저에 대한 이야기를 들어봤을 것 입니다. 클로저가 말로 설명하기에 난해한 정의 때문에 어려운 개념이라는 인식 이 있어서 다가가기가 쉽지 않으며 설명이 어려워 금방 잊어 버립니다. 특히, 면접 질문으로 자주 나오는데 어렴풋이 아는데 막상 설명하라고 하면 당황한 경험이 많이 있습니다. 까먹지 않기 위해 블로그로 다시 정리해보려고 합니다.
먼저 리액트에서 클로저를 빼놓고 설명할 수 없을 정도로 많은 관련이 있습니다. 함수형 컴포넌트 구조와 작동방식, 훅의 원리, 의존성 배열 등 모든 대부분이 클로저에 의존하고 있기 때문입니다. 그렇기 때문에 클로저를 꼭 알고 넘어가보도록 하죠
클로저란?
MDN 사전적 정의로는 아래와 같습니다. 정의를 보고서는 절대 이해할 수 없을 것 같아요
클로저는 함수와 함수가 선언된 어휘적 환경(Lexical Scope)의 조합
가장 먼저 '어휘적 환경(Lexical Scope)' 란 무엇일까?
먼저 아래 코드를 살펴봅시다.
function add() {
const a = 10;
function innerAdd() {
const b = 20;
console.log(a+b);
}
innerAdd()
}
add() // 어떤 값이 나올까?
위 코드에서 console.log 에 찍히는 값은 뭘까요?
정답은 30입니다. 이 예시와 같이 중첩돼어 있는 상황에서 변수의 범위가 어떻게 정의되는지 알 수 있을 것 입니다.
a 라는 변수의 유효범위는 add 전체이고, b의 유효범위는 innerAdd 전체입니다. innerAdd는 add 내부에 선언되어 있어서 a를 사용할 수 있게 된 것입니다. 변수가 코드 내부에서 어디서 선언됐는지 말하는 것을 '선언된 어휘적 환경' 이라고 합니다. 특징으로는 코드가 작성된 순간에 정적으로 결정되는데 클로저는 이러한 어휘적 환경을 조합하는 코딩 기법입니다.
유효 범위, 스코프(Scope)
클로저를 이해하기 위해서는 변수의 유효 범위에 따라서 어휘적 환경이 결정됩니다. 이러한 변수의 유효범위를 스코프(Scope)라고 하는데 자바스크립트에는 여러 스코프들이 존재합니다.
1. 전역 스코프
변수를 선언하면 어디든 호출할 수 있습니다
- 브라우저 환경: window
- Node.js 환경 : global
var global = 'global scope'
function hello() {
console.log(global)
}
console.log(global) // global scope
hello() // global scope
console.log(global === window.global) // true
2. 함수 스코프
자바스크립트는 기본적으로 함수 레벨 스코프입니다. 즉 {} 블록이 스코프 범위를 결정하지 않습니다
// {} 범위가 무조건 스코프가 아님
if (true) {
var global = 'global scope'
}
console.log(global) // global scope
console.log(global === window.global) // true
// 함수 레벨 스코프
function hello() {
var local = 'local variable'
console.log(local) // local variable
}
hello()
console.log(local) // local is not defined
만약 함수가 중첩이 되어 있다면 가장 가까운 스코프에서 변수(lexical variable)가 존재하는지 확인한 후 값이 달라질 수 있다는 것 입니다.
좀 더 복잡한 예시를 들자면 아래와 같습니다
var x = 1; // global
function first() {
var x = 10;
second();
}
function second() {
console.log(x);
}
first(); // ?
second(); // ?
first와 second 는 어떤 값으로 찍힐가?
정답은 1 1 이 찍힙니다. 이것을 이해하기 위해서는 렉시컬(Lexical Scope)에 대해 이해가 필요합니다.
렉시컬 스코프란(Lexical Scope)란?
함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정되는 것을 말한다.
선언 시점에 스코프가 결정되기 때문에 second의 x호출은 상위인 global을 호출하게 됩니다
이제 어휘적 환경과 스코프에 대해 알아봤으니 위 개념을 가지고 클로저에 대해 더 자세히 알아봅시다
클로저의 활용
클로저의 개념을 더 잘 알기위해 많이 설명되는 아래 예제 코드를 살펴봅시다
function outerFunction() {
var x = 'hello'
function innerFunction() {
console.log(x)
}
return innerFunction
}
const innerFunction = outerFunction()
innerFunction() // "hello"
위 스코프와 어휘적 환경을 알았기 때문에 'hello' 가 출력되는 것은 이해할 수 있을 것 입니다. 여기서 추가적으로 설명하자면 outerFunction은 innerFunction에 반환하며 종료가 됐습니다. innerFunction 자체에는 x라는 변수가 없지만 해당 함수가 선언된 어휘적 환경, 즉 outerFunction에 x라는 변수가 존재하며 접근할 수 있습니다. 즉, innerFunction은 선언된 시점을 기억하고 있다는 것 입니다.
바로 전 예시에서 '선언 시점에 스코프가 결정' 이라는 예시를 봤기 때문에 이제는 충분히 이해할 수 있을 것 입니다.
이해 하셨겠죠...??
리액트에서의 클로저
근데 막상 클로저를 알고 있지만 리액트에서 어떻게 활용하고 있는지 질문을 하실 수 있습니다
바로 리액트에서 가장 많이 사용하고 있는 것 중 하나인 'useState' 입니다
const Component = () => {
const [state. setState] = useState()
const handleClick = () => {
// ┌─────────────────────────────────────────────┐
// │ useState호출은 위에서 끝났지만, │
// │ setState는 계속 내부의 최신값(prev)를 알고 있습니다 │
// │ 이는 클로저를 활용한 대표 예시입니다 │
// └─────────────────────────────────────────────┘
setState((prev) => prev + 1)
}
// ...
}
useState 함수의 호출은 첫 줄에서 종료 되었지만 setState는 useState의 최신 값을 계속 확인하고 있습니다. 이는 외부 함수가 호출이 끝났음에도 자신이 선언된 외부 함수가 선언된 환경을 계속 기억하고 있기 때문입니다.
클로저의 또 하나의 장점은 '접근'에 있습니다
보통 전역에 상태를 저장하면 누구나 위 상태를 접근할 수 있기 때문에 앱에 버그를 일으킬 가능성이 높습니다. 리액트는 이 내부 상태를
useState라는 클로저를 활요해서 관리할 수 있고 setState라는 것을 통해 수정이 가능합니다
오늘은 클로저에 대해 알아보았습니다
막상 어려운 개념이 아니지만 이것을 활용하여 어떤 어려운 문제를 해결하기란 사실 쉽지 않습니다
잘못 사용할 경우 오히려 성능이 안좋거나 불필요한 메모리를 낭비할 수 있습니다
하지만 리액트의 핵심 개념이기 때문에 어느정도 설명 가능할 정도가 되면 좋을 것 같습니다
'프로그래밍 > React|Next.js' 카테고리의 다른 글
SupaBase 사용해보기 With Nextjs (0) | 2024.04.14 |
---|---|
사용 안하는 디펜던시 파일 지우기 (0) | 2024.04.04 |
[DeepDive] 1장 - 자바스크립트: 데이터 타입과 함수 (0) | 2024.03.11 |
gatsby의 이미지 컴포넌트 비교(StaticImage, GatsbyImage) (0) | 2023.08.08 |
React로 크롬 익스텐션(extension) 만들기 (1) 튜토리얼 (0) | 2023.07.31 |