최근 자바스크립트의
클로저
에 대해 공부하다가클로저
를 활용하여상태관리
를 할 수 있겠다는 생각이 들어, 기본적인상태관리
를 할 수 있는 함수를 구현해봤다.
클로저
는 사실 자바스크립트에만 존재하는 개념은 아니지만, 자바스크립트에서 굉장히 중요한 개념이다.
클로저
의 사전적인 의미는 내부 함수(중첩함수)
가 외부 함수 스코프
를 참조하는 함수를 말한다.
자바스크립트는 모든 함수가 외부 함수
의 스코프를 참조하고 있기 때문에 모두 클로저
다. 하지만 통상적으로 외부 함수
의 실행 컨텍스트
가 실행 컨텍스트 스택(콜스택)
에서 pop
되어 나가 생명 주기가 끝났지만, 내부 함수
가 외부함수(상위) 스코프
의 식별자
를 계속 참조하고 있는 함수만을 클로저
라고 한다.
외부 함수 스코프
의 식별자
를 내부 함수
에서만 접근할 수 있기 때문에 클로저
를 활용하여 식별자
의 값 변경을 제어할 수 있다는 특징이 있다. 값의 변경을 제어할 수 있다면, 응용하여 상태 관리
에 활용해 볼 수 있겠다는 생각이 들었다.
리액트
를 할 때는 별 생각 없이 자연스럽게 사용했었는데,상태 관리
함수를 구현하다 보니 이거 완전 리액트랑 똑같자나? 라는 생각이 들어서 (물론 내부적으로는 더 복잡하겠지만) 네이밍을 리액트 hooks
와 동일하게 지었다.
const [state, setState] = useState('abc');
const useState = (() => {
let state;
const setState = newState => state = newState;
return (newState) => {
setState(newState);
return [state, setState];
}
})();
우선 외부 함수
의 생명주기가 내부 함수
보다 짧게 하기 위해, 외부 함수
를 즉시 실행 함수
를 만들고 실행했다. 일반 함수라면 함수 스코프
의 식별자
는 함수
가 호출된 이후에 참조할 수 없지만, 외부 함수
스코프의 식별자
는 외부 함수
의 호출에 의해 반환되는 내부 함수
인 익명 함수가 useState
식별자에 담겨져 참조되어 지고 있다.
const [state, setState] = useState('before');
console.log(state); // 'before'
즉시 실행 함수
가 반환하는 익명 함수
가 배열
을 반환하기 때문에 useState
에는 배열이 값으로 담겨지고, state
와 setState
에 각각 배열의 원소가 비구조화 할당
되어 담긴다.
state
는 const
로 선언되었기 때문에 값을 재할당할 수 없지만, setState
함수를 통해 state
의 값을 변경할 수 있다. 그런데, 리액트
는 setState
함수가 호출되면 내부적으로 리렌더링
를 통해 state
의 값이 변환되지만, 변환된 값을 새로운 변수에 재할당
하지 않고 변환된 값을 확인할 수가 없었다.
setState('after');
console.log(state); // 'before'
const useState = (() => {
let state;
const getState = () => state;
const setState = newState => state = newState;
return (newState) => {
setState(newState);
return [getState, setState];
}
})();
const [state, setState] = useState('before');
console.log(state()); // 'before'
setState('after');
console.log(state()); // 'after'
state
의 값을 확인하기 위해 매번 함수를 호출
해야 한다는 단점이 있지만, 이제 state
를 은닉하여 setState
를 통해서만 값을 변환할 수 있다. setState
로 값을 변환한 값도 바로 확인할 수 있다.
const [state, setState] = useState(1);
console.log(state()); // 1
setState(state() + 1);
console.log(state()); // 2
const useState = (() => {
let state;
const getState = () => state;
const setState = newState => {
const stateType = typeof _state;
if( stateType !== 'undefined' && stateType !== typeof newState )
throw new Error(`${newState} is not ${stateType}`)
state = newState;
};
return (newState) => {
setState(newState);
return [getState, setState];
}
})();
const [state, setState] = useState('before');
console.log(state()); // 'before'
setState(100); // Error: 100 is not string
console.log(state());
함수는 자바스크립트
로 구현했기 때문에 타입에 대한 확인을 하지 않아서, setState
함수 내부에서 이전 state
와 변경하려는 state
의 타입
을 체크하는 로직을 별도로 추가했다.