카카오톡 오픈 채팅방에 따르면 요즘의 기업들은 클래스형이 아닌 훅스를 사용한다고 한다. 남들하는건 다 할줄 알아야지.. 그래서 공식 문서를 하나씩 읽어보기 시작했다.
ko.reactjs.org/docs/hooks-state.html
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0); // setCount를 통해 count 값이 바뀌는데 어째서 count인가?
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위의 코드는 읽어보면 충분히 이해할 수 있는 쉬운 내용이다. 그런데 의문이 생긴 부분이 있었다. 바로 useState 훅을 통해 전달되는 값이 const로 선언되어 있던 것이다. setCount의 경우에는 함수이니 재할당을 방지하기 위해서 const로 선언하는 것이 이해가 가지만, count의 경우에는 setCount를 통해서 값이 바뀌어야 하는데 let으로 선언해야하지 않나? 왜 오류가 생기지 않는거지? 하는 궁금증이 생겼다. 훅스를 처음 배우기 시작한 사람이라면 아마 나와같은 생각을 할 것이다.
hewonjeong.github.io/deep-dive-how-do-react-hooks-really-work-ko/
그래서 검색을 하던 중에 좋은 글을 발견하게 되었다. 리액트 훅스 공부를 시작하게 된 사람이라면 꼭 읽어보기를 바란다. 이 글과 나의 주관적인 생각을 바탕으로 하여 useState의 전달 값을 왜 const로 선언했는지에 대해서 단계별로 설명해보도록 하겠다.
1. const란 무엇이고 언제 쓰이는가?
이 게시물을 읽는 사람들 중에서 js의 const 문법을 모르는 사람은 없을 것이라고 생각된다. 그렇다면 그 용도는 무엇일까. 수많은 사람들과 하나의 코드를 공유하여 작성할 때, 타인이 작성한 모든 코드들을 달달달 외우고 있지는 않을 것이다. 그런데 각자가 코드를 수정하는 과정에서 다른 사람의 의도를 놓쳐버려서 잘못된 수정을 했다고 생각해보자. 10000줄이 넘는 코드에서 오류가 난다면? 그 이유를 찾는 과정은 끔찍할 것이다. 나는 다른 팀원들과 큰 규모 프로젝트를 해본 적 경험이 없기 때문에 이런 경험이 없지만, 아마 미쳐버릴지도 모른다.
갑자기 다른 이야기이기는 하지만 js에서 하도 강조하는 불변성도 비슷한 맥락인 것 같다. (다음에 기회가 되면 포스팅하도록 하겠다.) 결국 이러한 개념들은 사실 컴퓨터 단계에서 기능을 위함이라기보다는, 개발자들간의 가독성과 유지/보수성을 위한 것이라고 볼 수 있다. (필자의 생각임.)
// 수정전
var test = function (a) { // 'test'라는 겹칠 수도 있는 변수 이름
console.log(a);
}
/* 예를들어 1000000000000000000000000000줄의 코드... */
test('hello') // 개발자는 'hello'의 출력을 의도함
// 코드의 1000000000000000000000000000줄 뒤의 내용을 모르는 개발자가 수정한 코드
var test = function (a) { // 'test'라는 겹칠 수도 있는 변수 이름
console.log(a);
}
/* 1000000000000000000000000000줄 속 코드의 어딘가... */
// 새롭게 추가된 내용
test = function () {
console.log('bye');
}
/* 1000000000000000000000000000줄 속 코드의 어딘가... */
test('hello') // 이전 개발자는 'hello'의 출력을 의도했지만 이제 'bye'가 출력됨
이러한 관점에서 바라보았을 때 React에서는 count 변수를 직접 수정하는것을 금지하고. setCount로만 수정할 수 있게 의도하는 의미에서 const로 선언을 한 것이다. (class형에서 setState를 통해서만 state를 수정하는 것 처럼)
2. 그런데 state값은 바뀐다??
import React, { useState } from 'react';
function Example() {
const [count, setCount] = useState(0); // setCount를 통해 count 값이 바뀌는데 어째서 count인가?
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
위의 내용처럼 const로 선언 했다면 값은 바뀌지 않아야한다. 하지만 위의 코드 예시만 보아도 setCount를 통해서 count값이 바뀌는 것은 너무나 분명해보인다. 이게 어떻게 된 일일까?
hewonjeong.github.io/deep-dive-how-do-react-hooks-really-work-ko/
위의 글을 읽을 정독하면 그 이유를 알 수 있다. 하지만 글의 길이가 길기 때문에 쉽게 정리해 보겠다.
주의사항: 클로저에 대한 이해가 필요하지만 이 글에서 설명은 생략하겠음.
(클로저에 대해 간단하게 설명하자면 함수 내부의 변수가 함수 수명이 끝나더라도, 변수의 참조가 계속 된다면 그 변수의 수명은 계속 된다고 설명할 수 있다.)
코드에 주석을 달아 놓았으니 꼭 꼭 씹어서 소화하길 바란다.
// MyReact
const MyReact = (function() {
let hooks = [] // state 값들이 저장되는 배열
let currentHook = 0 // 새로운 state가 시작될 index
return {
render(Component) {
const Comp = Component() // Counter 객체, useState 생성
Comp.render() // 'render', { count, text } 출력
currentHook = 0
// compnentHook이 render 될 때마다 0으로 초기화 되는 이유는
// 다음 컴포넌트의 render를 위해서
// 인덱스가 0부터 시작하여 useState의 호출 횟수와 같아야 하기 때문이다.
// component의 가장 최근 state들은 component의 return 과정에서
// 클로저에 의하여 setCount, setText의 인자 형태로 저장되어있다.
// ex) setCount(count + 1)
// setState가 실행되면 setStateHookIndex의 위치에 newState 값으로 덮어씌여진다.
// hooks 배열에는 가장 최근에 setState 된 값들이 저장되는 것이다.
// 이후 componet의 useState가 호출되면서
// hooks[currentHook] 값들에 알맞게 대응되어 새로운 state값을 반환한다.
// render를 해줘야 새로운 useState가 호출되고
// 클로저 개념으로 인해 state 값들이 컴포넌트의 onClick 함수의 파라미터로 저장이 된다.
// 그렇기 때문에 setState를 호출하면 무조건 !!렌더링!!을 해주어야 값이 섞이지 않는다.
// 과정이 끝나게 되면 다음 컴포넌트를 위해 currentHook을 다시 0으로 초기화해준다.
return Comp // Counter 객체가 반환됨
},
useState(initialValue) {
hooks[currentHook] = hooks[currentHook] || initialValue
const setStateHookIndex = currentHook
const setState = (newState) => (hooks[setStateHookIndex] = newState)
// setState에서 클로저의 개념으로 인해 setStateHookIndex 값은 저장된다.
return [hooks[currentHook++], setState]
},
}
})()
function Counter() {
const [count, setCount] = MyReact.useState(0)
return {
click: () => {
setCount(count + 1)
},
render: () => console.log('render1', { count }),
}
}
function Counter2() {
const [count, setCount] = MyReact.useState(0)
const [text, setText] = MyReact.useState('')
// currentHook은 2가 됨
return {
click: () => {
setCount(count + 10)
setText(text + 'var ')
},
render: () => console.log('render2', { count, text }),
}
}
let App = MyReact.render(Counter)
let App2 = MyReact.render(Counter2)
App.click()
App = MyReact.render(Counter)
App2.click()
App2 = MyReact.render(Counter2)
App.click()
App = MyReact.render(Counter)
App2.click()
App2 = MyReact.render(Counter2)
1. component의 setState가 이전 state의 값을 클로저 형태로 저장하고 있으며,
2. setState가 호출되면서 MyReact의 hooks 값을 갱신하고,
3. hooks의 값들을 useState로 가져와 새로운 component를 렌더링하는 과정을 거친다고 볼 수 있다.
결론:
객체의 속성처럼 생각하면 컴포넌트의 state 값들이 변경된다고 생각할 수 있다. 그래서 useState의 실제로 동작하는 원리를 자세히 뜯어보았다. 그런데 이전의 state 변수는 컴포넌트 함수가 실행되면서 매번 새로운 const 변수가 실행된다는 것을 알 수 있었다. const로 선언함으로 state 변수를 직접 수정하는 것을 방지하고, setState를 사용하게 하기 위함이 const로 선언되는 이유라고 할 수 있겠다.
'react' 카테고리의 다른 글
useContext 실습 (0) | 2020.09.16 |
---|---|
useMemo 실습 (0) | 2020.09.16 |
react-redux 실습 (0) | 2020.07.27 |
router 실습 (0) | 2020.07.08 |