참고
https://ko.reactjs.org/docs/hooks-state.html
https://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Make-useSate-hook/
React Hook의 기본이자 가장 많이 사용하는 useState에 대해 정리하면서 씹고 뜯고 맛봐보자
const [state, setState] = useState(initialState);
useState함수를 호출하면 파라미터로 전달한 initialState값을 초기값으로 갖는
state(상태 변수)와 setState(변수를 수정할 수 있는 setter 함수)를 배열로 반환한다.
useState
를 호출하는 것은 무엇을 하는 걸까?“state 변수”를 선언할 수 있다. (위에 선언한 변수는
state
라고 부르지만banana
처럼 아무 이름으로 지어도 된다) 일반 변수는 함수가 끝날 때 사라지지만, state 변수는 React에 의해 사라지지 않습니다.
useState
의 인자로 무엇을 넘겨주어야 할까?*
useState()
Hook의 인자로 넘겨주는 값은 state의 초기 값으로 숫자 타입과 문자 타입을 가질 수 있다. (2개의 다른 변수를 저장하기를 원한다면useState()
를 두 번 호출해야 함)
useState
는 무엇을 반환할까?[state 변수, 해당 변수를 갱신할 수 있는 함수] 이 두 가지 쌍을 반환한다. 이는 useState Hook을
const [count, setCount] = useState()
처럼 쓰는 이유이다.
createState
가 아닌, useState
로 이름을 지었을까?컴포넌트가 렌더링할 때 오직 한 번만 생성되기 때문에 “Create”라는 이름은 꽤 정확하지 않을 수 있습니다. 컴포넌트가 다음 렌더링을 하는 동안 useState
는 현재 state를 줍니다. - React 공식문서
useState의 동작을 살펴보면
function Counter () {
const [count, setCount] = useState(1);
// 돔에서 직접 호출하기 위해 window(전역객체)에 할당
window.increment = () => setCount(count + 1);
return `
<div>
<strong>count: ${count} </strong>
<button>증가</button>
</div>
`;
}
- useState로 state와 setState를 만들 수 있다.
- 500ms(0.5초)마다 setCount를 실행한다.
- 값이 1씩 증가한다.
- setCount가 실행되면 다시 렌더링이 실행된다.
- 렌더링이 실행되면 Counter가 다시 실행될 것이다.
- Counter 컴포넌트가 다시 실행되어도 count의 값은 초기화되지 않고 유지된다.
이렇게 진행이 되는데, 6번 컴포넌트 함수가 다시 실행 되더라도 count의 값이 초기화되지 않고 유지된다 는 어떻게 가능할까?
count의 값이 어떻게 초기화되지 않고 유지 되는지를 중심으로 차근차근 useState
에 대해 분석해보자.
아래는 Counter
컴포넌트를 렌더링 해주는 코드이다.
/** Javascript **/
function useState (initState) { }
function Counter () {
const [count, setCount] = useState(1);
window.increment = () => setCount(count + 1);
return `
<div>
<strong>count: ${count} </strong>
<button>증가</button>
</div>
`;
}
function render () {
const $app = document.querySelector('#app');
$app.innerHTML = Counter();
}
render();
useState
를 실행하면 첫 번째 인자는 state
를 반환하고, 두 번째 인자는 state를 변경하는 setState
를 반환하다. 그리고 setState
를 실행하면 render
가 실행된다.
그래서 대략 다음과 같은 형태의 코드가 될 것이다.
function useState(initState) {
let state = initState; // state를 정의한다.
const setState = (newState) => {
state = newState; // 새로운 state를 할당한다
render(); // render를 실행한다.
}
return [ state, setState ];
}
useState
를 실행하면 내부에 state를 정의하고, setState를 실행하면 내부에 선언된 state를 변경할 것이다. 즉, 함수가 실행될 때 마다 결국 state의 값은 initState
로 초기화 될 것이다.
예를 들어 다음과 같을 때
const [count, setCount] = useState(1); // state에는 항상 1이 들어간다.
state에는 항상 1이 들어간다.
그래서 state
의 값은 내부가 아닌 외부에서 관리해야 한다.
let state = undefined;
function useState(initState) {
// state에 값이 없을 때만 초기화를 진행한다.
if (state === undefined) {}
state = initState;
}
const setState = (newState) => {
state = newState; // 새로운 state를 할당한다
render(); // render를 실행한다.
}
return [ state, setState ];
}
function Counter() { /*생략*/ }
function render () { /*생략*/ }
render();
렌더링 할 때 마다 초기화되지 않고 잘 작동한다.
그렇다면 만약 useState와 Component가 여러 개라면 어떨까?
function Counter () {
const [count, setCount] = useState(1);
window.increment = () => setCount(count + 1);
return `
<div>
<strong>count: ${count} </strong>
<button>증가</button>
</div>
`;
}
function Cat () {
const [cat, setCat] = useState('고양이');
window.meow = () => setCat(cat + ' 야옹!');
return `
<div>
<strong>${cat}</strong>
<button>고양이의 울음소리</button>
</div>
`;
}
function render () {
app.innerHTML = `
<div>
${Counter()}
${Cat()}
</div>
`;
}
한 개의 state 변수로 두 개의 state를 관리하기 때문에 count와 cat이 똑같은 값을 보여주게 된다.
이를 해결하기 위해서 외부의 state 갯수를 useState가 실행되는 횟수만큼 만들어주면 될 것이다.
let currentStateKey = 0; // useState가 실행 된 횟수
const states = []; // state를 보관할 배열
function useState(initState) {
// initState로 초기값 설정
if (states.length === currentStateKey) {
states.push(initState);
}
// state 할당
const state = states[currentStateKey];
const setState = (newState) => {
// state를 직접 수정하는 것이 아닌, states 내부의 값을 수정
states[currentStateKey] = newState;
render();
}
currentStateKey += 1;
return [ state, setState ];
}
function Counter () { /*생략*/ }
function Cat () { /*생략*/ }
const render = () => {
app.innerHTML = `
<div>
${Counter()}
${Cat()}
</div>
`;
// 이 시점에 currentStateKey는 2가 될 것이다.
// 그래서 다시 0부터 접근할 수 있도록 값을 초기화 해야 한다.
currentStateKey = 0;
}
여기까지가 useState의 핵심원리 라고 할 수 있다.
이렇게 정리하면서 느낀것은 useState의 원리가 클로저와 비슷하다는 것이다. 직접적으로 클로저를 사용하진 않았지만 유사한 개념이라고 볼 수 있다.
function 클로저() {
let 죽지않는_변수 = 0;
return function () {
죽지않는_변수 += 1;
console.log('죽지않는_변수 : ' + 죽지않는_변수);
}
}
const 클로저로_만들어진_함수 = 클로저();
클로저로_만들어진_함수(); // 죽지않는_변수 : 1
클로저로_만들어진_함수(); // 죽지않는_변수 : 2
클로저로_만들어진_함수(); // 죽지않는_변수 : 3
클로저로_만들어진_함수(); // 죽지않는_변수 : 4
클로저의 개념처럼 useState
함수의 바깥에서 state
를 관리하기 때문에 state
의 값이 유지되는 것이다.
useState에 대해 정리하면서 자료를 찾아보다가 정말 좋은 글이 있어서 글을 따라 동작원리를 분석해보았다. 당연하게 생각했던 부분을 뜯어보니 당연하지 않고 다 이유가 있다.
그리고 클로저의 원리를 이렇게 가까운 곳에서 사용하고 있었다니,,, 괜히 클로저 클로저 하는게 아니다.