[글또] Closure의 원리를 이용해서 useState 구현하기(2)
지난 시간에는 Closure의 원리를 이용해 useState를 간단하게 구현해보았다.
이번주에는 조금 더 깊이 있게 들어가보려고 한다.
useMyState 여러개 사용하면 정상적으로 동작하지 않는다?!
우리가 지난 시간에 만들었던 useMyState를 여러개 사용하게 되면
정상적으로 동작하지 않는 모습을 확인할 수 있다.
// ... 생략
function Component() {
const [count, setCount] = React.useMyState(1)
const [text, setText] = React.useMyState('apple')
return {
render: () => console.log({ count, text }),
click: () => setCount(count + 1),
type: (word) => setText(word),
}
}
var App = React.render(Component) // { count: 1, text: "apple"}
App.click()
var App = React.render(Component) // { count: 2, text: 2 }
App.type('pear')
var App = React.render(Component) // { count: "pear", text: "pear"}
그 이유는, React 모듈 값은 _val 하나이기 때문이다.
갱신하는 함수를 호출할 때마다 _val을 덮어쓰기 때문에, 항상 같은 값으로 갱신이 된다.
이를 해결할 수 있는 방법이 있는데
그것은 바로 "배열"과 "인덱스"를 활용하는 것!
const React = (function () {
let hooks = []
let idx = 0
function useMyState(initVal) {
const state = hooks[idx] || initVal
const setState = (newVal) => {
hooks[idx] = newVal
}
idx += 1
return [state, setState]
}
function render(Component) {
const C = Component()
C.render()
console.log(idx) // 2, 4, 6
return C
}
return { useMyState, render }
})()
function Component() {
const [count, setCount] = React.useMyState(1)
const [text, setText] = React.useMyState('apple')
return {
render: () => console.log({ count, text }),
click: () => setCount(count + 1),
type: (word) => setText(word),
}
}
var App = React.render(Component) // { count: 1, text: 'apple' }
App.click()
var App = React.render(Component) // { count: 2, text: 'apple' }
App.type('pear')
var App = React.render(Component) // { count: 'pear', text: 'apple' }
여기서 pear를 추가한 순간 원래 text에 갱신이 되어야했지만 count에 변경 되었다.
그 이유는 React.render를 하는 순간 Component가 useState 함수를 호출하게 되고 자동으로 idx가 추가가 되기 때문이다.
그래서 이미 증가된 idx를 통해 갱신을 하다보니 값이 이상해진다.
이를 고치기 위해 살짝 수정해보자면
const React = (function () {
let hooks = []
let idx = 0
function useMyState(initVal) {
const state = hooks[idx] || initVal
const setState = (newVal) => {
hooks[idx] = newVal
}
idx += 1
return [state, setState]
}
function render(Component) {
idx = 0
const C = Component()
C.render()
return C
}
return { useMyState, render }
})()
function Component() {
const [count, setCount] = React.useMyState(1)
const [text, setText] = React.useMyState('apple')
return {
render: () => console.log({ count, text }),
click: () => setCount(count + 1),
type: (word) => setText(word),
}
}
var App = React.render(Component) // { count: 1, text: 'apple' }
App.click()
var App = React.render(Component) // { count: 1, text: 'apple' }
App.type('pear')
var App = React.render(Component) // { count: 1, text: 'apple' }
// [ <2 empty items>, 2 ]
// [ <2 empty items>, 'pear' ]
그런데, idx를 render시 0으로 초기화 해줬더니 아예 상태가 바뀌지 않는 현상이 일어난다.
이 현상은 당연하게도 render가 먼저 발생하고 그 이후 setState가 발생하면서
idx가 계속 0으로 들어가기 때문에 hooks 배열에 값이 저장되지 못하고
initVal이 계속 state를 대신한 값이 되어버리기 때문!
그리고 두 번 호출 이후 idx가 3이 되었을 때 들어가기 때문에 값은 세 번째에 들어가게 되는 것,
그렇다면 이렇게 변동되는 idx가 문제라는 것을 알았으니 이를 고정해서 해결해보면
function useMyState(initVal) {
const state = hooks[idx] || initVal
const _idx = idx // freeze
const setState = (newVal) => {
hooks[_idx] = newVal
}
idx += 1
return [state, setState]
}
var App = React.render(Component) // { count: 1, text: 'apple' }
App.click()
var App = React.render(Component) // { count: 2, text: 'apple' }
App.type('pear')
var App = React.render(Component) // { count: 2, text: 'pear' }
idx가 component render 이후 idx가 증가했더라도
useState만의 고유한 idx가 있기 때문에 그에 맞는 상태를 변경할 수 있게 된다
hook은 호출에 대한 순서보장이 필요합니다
공식문서에도 명시되어 있듯이 hook은 반복문, 조건문 및 중첩된 함수에서 사용하면 안된다.
그 이유는 동일한 순서로 호출이 되어야 하기 때문이다.
function Component() {
if (Math.random() > 0.5) {
const [count, setCount] = React.useState(1) // ReferenceError: count is not defined
}
const [text, setText] = React.useState('apple')
return {
render: () => console.log({ count, text }),
click: () => setCount(count + 1),
type: (word) => setText(word),
}
}
원래는 hook을 호출한 순간 각각의 상태에 대한 배열의 idx를 부여받고 그 곳에서 상태를 관리하지만,
만약 특정 조건에 따라서 hook이 생성된다면 idx가 변경될 여지가 있기 때문이다.
이처럼 리액트 훅은 클로저라는 개념을 사용하여 private한 값을 활용하여 구성되어있다.
이번 기회를 통해 Closure의 원리를 코드로 적용해보았다.
Closure에 대한 개념적 이해에서 나아가 어떻게 돌아가는 건지 간단하게 이해할 수 있었고,
Hook의 원리를 조금 더 알게된다면 오픈소스를 뜯어보고 싶다.
다음 글에서는 React 상태에 대해서 다뤄보려고한다.
React 상태관리 매커니즘은 어떻게 돌아가는지,
핵심 아키텍쳐는 무엇인지 알아보고 상태 관리가
성능에 어떤 영향을 미치는지에 대해서도 알아볼 예정이다.