리액트의 useState는 모듈 패턴을 바탕으로 구현되어 있습니다.
모듈 패턴을 이해하려면 클로저를 이해해야 되는데, 클로저에 대해 헷갈린다면 여기를 참조하면 이해가 쉬울 것 같습니다.
저도 헷갈리던 것들을 잘 정리할 수 있었습니다.
1~2편에 걸쳐서 리액트 모듈을 구현해 보겠습니다.
참고한 유튜브 영상을 보시면 이해하는 데 더더욱 도움이 될 것 같습니다.
state를 보관하고, render와 useState 메소드를 가진 MyReact를 먼저 구현해 보겠습니다.
const MyReact = (function () {
let _val; // 우리가 관리할 state에 해당합니다.
return {
render(Component) {
const comp = new Component();
comp.render();
return comp;
},
useState(initialValue) {
_val = _val || initialValue;
function setState(newValue) {
_val = newValue;
}
return [_val, setState];
},
};
})();
즉시실행함수로 모듈패턴을 통해 MyReact를 구현했습니다.
render 메서드는 Component를 인자로 받아 해당 컴포넌트를 렌더링할텐데요,
아래 코드를 보시면 알겠지만 브라우저가 아닌 콘솔에 찍을 예정입니다.
useState는 _val이라는 변수를 참조하고 있습니다.
이렇게 되면 클로저가 형성됩니다. 우리가 컴포넌트 내에서 useState를 호출하면,
"지금" 형성된 외부 렉시컬 환경인 MyReact 내에서 _val을 찾을 것입니다.
다음은 컴포넌트입니다.
function Counter() {
const [count, setCount] = MyReact.useState(0);
return {
render() {
console.log('render: ', count);
},
click() {
setCount(count + 1);
},
};
}
위에서 언급한 대로, 렌더링은 콘솔로 대체하고 있습니다.
useState를 MyReact 모듈에서 가져와 반환값인 state와 setState를 count와 setCount에 할당했습니다.
전체 코드를 통해, useState가 어떻게 동작하는지 보겠습니다.
const MyReact = (function () {
let _val;
return {
render(Component) {
// 2. Counter 컴포넌트 함수의 코드가 한줄 한줄 실행됩니다.
const comp = new Component();
// 4. 컴포넌트가 렌더링(콘솔에 찍기)됩니다.
comp.render();
return comp;
},
useState(initialValue) {
_val = _val || initialValue;
function setState(newValue) {
_val = newValue;
}
return [_val, setState];
},
};
})();
function Counter() {
// 3. useState가 호출되어, _val에 0을 할당하고 setState와 함께 반환합니다.
const [count, setCount] = MyReact.useState(0);
return {
render() {
console.log('render: ', count);
},
click() {
// 7. setCount는 외부 렉시컬 환경(MyReact)의 _val을 찾아 값을 수정합니다.
setCount(count + 1);
},
};
}
// 1. MyReact 모듈의 render메서드를 Counter를 인자로 전달하여 호출했습니다.
// 5. App 변수에 Counter 함수가 반환한 객체가 할당됩니다.
let App = MyReact.render(Counter);
// 6. click을 통해 setCount가 호출되었습니다.
App.click();
// 8. render메소드를 다시 해봅니다.
App.render(); // 9. render: 0 ??
0이 출력되는 이유가 무엇일까요?
컴포넌트 객체(App)이 render를 통해 count라는 변수를 참조하고 있습니다.
하지만, 해당 count는 아직 업데이트 되지 않은 값입니다.
count가 업데이트가 되려면, 함수가 재호출 되어야 합니다. 아래 코드처럼요.
App = MyReact.render(Counter); // render: 1
실제로 리액트에서 함수형 컴포넌트는 state가 업데이트 되면서 함수 전체가 다시 호출됩니다.
그래야 useState가 다시 한번 호출되고, 새 state를 받아 올 수 있기 때문입니다.
다음 2편에서는 useEffect를 구현해 보겠습니다.