React hook중 useState를 다들 한번쯤은 사용해 봤을 것입니다.
분명 const로 count와 setCount를 선언 했기에 재 할당이 되지 않는데 어떻게 값을 바꿀수 있는것인가? 내부적으로 어떻게 동작하는거지? 라는 궁금중이 생겼습니다.
const [count, setCount] = React.useState(0);
다음과 같이 커스텀 useState를 만들어 보겠습니다.
function useState(initialValue) {
let value = initialValue; //지역변수 value
function state() { //value값을 리턴
return value;
}
function setState(newValue) {
value = newValue; // value값을 재 할당 해준다.
}
return[state, setState];
}
const [count, setCount] = useState(0);
console.log(count()); //0
setCount(3); // 새로운 값 할당
console.log(count()); //3
다음과 같이 useState의 기본적인 형태의 복사본을 만들었습니다.
위 함수를 이해하기 위해서는 클로저의 개념이 필요합니다.
useState 함수내에 지역변수 value가 선언, 할당 되어있고, state 함수는 value를 리턴해주게 되고, setState는 외부 함수의 value에 인자로 들어온 newValue를 재 할당 해주게 됩니다.
const [count, setCount] = useState(0);
useState 함수를 실행하게 되면 내부에 있는 state와 setState는 클로저의 특징인 외부 함수의 value 값을 참조하고 있는 형태게 됩니다.
그래서 setCount 함수를 실행 했을 때 value값이 업데이트되어 count함수 내부의 value가 업데이트 된 값을 받아볼 수 있는것을 확인할 수 있습니다.
여기서 의문점이 그냥 value를 내보내주면 setState가 업데이트 될 때 변경할 수 있지 않을까? 라는 생각이 들었습니다. state를 없애고 value만 return해주니 다음과 같은 결과가 나왔습니다.
function useState(initialValue) {
let value = initialValue; //지역변수 value
function setState(newValue) {
value = newValue; // value값을 재 할당 해준다.
}
return[value, setState];
}
const [count, setCount] = useState(0);
console.log(count()); //0
setCount(3); // 새로운 값 할당
console.log(count()); //0
왜 이런것일까.. 이유는 간단합니다. value는 그냥 원시값이기 때문. 원시값을 받아와 할당 해주는것이기 때문에 useState의 value와는 전혀 상관이 없는 값이 돼 버렸기 때문입니다.
배열이나 객체를 넣어줬을 때는 문제가 없겠죠?? 한번 직접 해보시길 바랍니다!
hook을 흉내 냈을 뿐 react의 hook과는 매우 달라 보입니다. 조금더 수정을 해주겠습니다.
const MyReact = (function() {
let _val // 모듈 스코프 안에 state를 선업합니다.
return {
render(Component) { //render함수를 호출할 때마다 Component를 재 생성 합니다.
const Comp = Component()
Comp.render()
return Comp
},
useState(initialValue) {
_val = _val || initialValue // val가 있으면 val값을 업데이트하고, 없다면 initialValue값을 넣어줍니다.
function setState(newVal) { //이전과 같습니다.
_val = newVal
}
return [_val, setState]
},
}
})()
function Counter() {
const [count, setCount] = MyReact.useState(1)
return {
click: () => setCount(count + 1),
render: () => console.log(count),
}
}
let App
App = MyReact.render(Counter) // 1
App.click()
App = MyReact.render(Counter) // 2
이제야 좀 React hooks 와 비슷해 졌습니다.
위 함수에 설명을 하자면, MyReact 함수는 지역 변수로 val을 갖고 있습니다.
render함수는 state의 값이 변하면 리 렌더링 해주는 React 컴포넌트로 보시면 됩니다.
useState가 조금 변경 되었는데 _val 의 값이 비어 있으면 initialValue를 /
_val이 비어있지 않으면 _val 값을 넣어줍니다.
왜 _val을 넣어주는걸까요?
React는 기본적으로 state가 업데이트 되면 컴포넌트를 리 렌더링 하게 되어있습니다.
컴포넌트를 리 렌더링 한다는 것은 즉 컴포넌트를 다시 한번 실행 시킨다는 것으로, 내부에 있는 변수들이 재 선언이 이뤄진다는 것을 예상할 수 있습니다.
이때, const [count, setCount] = MyReact.useState(1)
이 부분이 재 선언이 되어, MyReact.useState의 인자로 (1)이 들어가게 되어 _val값을 확인하는 이유 입니다.
click함수를 여러번 실행 시킨뒤 MyReact.render를 해보시면 어떤 결과가 나오나요?
click함수를 10번 시키든 100번 시키던 render 함수를 실행시키지 않으면 값이 업데이트 되지 않습니다.
App = MyReact.render(Counter) // 1
App.click()
App.click()
App.click()
App.click()
App.click()
App.click()
App = MyReact.render(Counter) // 2
다음과 같은 결과가 나오는 이유는 리렌더링이 이뤄지지 않았기 때문입니다.
위 함수는 click함수 실행시 count+1이 업데이트 되어 저장되지만, Counter컴포넌트가 리렌더링이 발생하지 않아, count는 그대로 1이기 때문에 마지막 render 함수 호출시 2가 출력되게 되는것 입니다.
참고 - https://hewonjeong.github.io/deep-dive-how-do-react-hooks-really-work-ko/