useState란?
state는 컴포넌트가 가지고 있는 속성값이다. 이 속성값이 변하면 react는 자동으로 UI를 업데이트 시킨다.
다음 예제에서 name은 state가 아니고 단순 변수이기 때문에 버튼을 클릭해도 화면에 표시되는 이름이 바뀌지 않는다.
export default function Hello() {
let name = "Mike";
function changeName() {
name = name === "Mike" ? "Jane" : "Mike";
}
return (
<div>
<h1>state</h1>
<h2>{name}</h2>
<button onClick={changeName}>Change</button>
</div>
);
}
state로 만들기 위하여 react hook 중 하나인 useState를 사용한다.
https://legacy.reactjs.org/docs/hooks-reference.html
hook은 react 16.8 버전부터 사용할 수 있다. 초기 리액트는 state와 lifecycle을 관리하기 위해 클래스형 컴포넌트를 만들어야 했고, UI를 표현하는 컴포넌트만 함수형으로 제작했다. 하지만 16.8부터는 모든 컴포넌트를 함수형으로 만들 수 있게 되었고, react hook를 이용하여 함수형 컴포넌트에서도 state와 lifecycle 관리가 가능해졌다.
useState 사용법
const [상태 변수, 상태 변경 함수] = useState(초기값);
useState를 선언할 때는 반드시 let이 아닌 const를 사용해야 한다. let을 사용하면 상태 변경 함수로 state를 업데이트 하지 않고 변수처럼 직접 state를 바꿀 수 있기 때문이다.
useState의 내부 동작 구현해보기
useState는 Javascript의 클로저라는 특징을 이용한다. (다음 게시물 참고)
https://winterflower.tistory.com/165
다음 코드는 useState의 내부를 간단하게 구현해본 것이다. (실제로는 더 복잡하게 구현되어 있을 것이다)
const MyReact = (function() {
let _val
return {
render(Component) {
const Comp = Component()
Comp.render()
return Comp
},
useState(initialValue) {
_val = _val || initialValue
function setState(newVal) {
_val = newVal
}
return [_val, setState]
}
}
})()
MyReact는 두 개의 closure를 반환하고 있다. _val은 상태를 저장하는 변수다. useState가 _val을 참고하고 있기 때문에 closure 원리에 따라 _val의 값은 없어지지 않고 유지된다!!
useState에서 _val은 처음에 undefined일 경우 initialValue를 할당한다. 이후 useState가 다시 호출될 때는 _val에 할당된 값이 있으므로 기존의 값을 그대로 사용한다. useState안에 setState를 통해 _val의 값을 업데이트 할 수 있다.
function Counter() {
const [count, setCount] = MyReact.useState(0)
return {
click: () => setCount(count + 1),
render: () => console.log('render:', { count })
}
}
let App
App = MyReact.render(Counter) // render: { count: 0 }
App.click() // rerendering
App = MyReact.render(Counter) // render: { count: 1 }
Counter을 하나의 컴포넌트로 생각해 보자.
처음에 MyReact.render(Counter)를 실행하면 useState에서 _val에 초기값 0을 할당한다.
App.click()으로 _val이 1로 업데이트된다. 실제 React에서는 state 값이 변경되었기 때문에 컴포넌트를 리렌더링 할 것이다. 그 과정을 추가하기 위해 App.click() 후 render를 실행해 줬다.
다시 렌더링 하면 _val이 1로 유지되어 있는 것을 확인할 수 있다.
setState가 비동기로 동작하는 이유
const [count, setCount] = useState(0);
useEffect(() => {
setCount(count + 1);
console.log(count); // 0
}, []);
setCount 후에 console.log에 새로운 값 반영이 되지 않는 이유가 무엇일까?
react는 상태값이 변경되면 렌더링 함수를 호출해 가상 돔을 업데이트하고 가상 돔과 실제 돔을 비교해서 실제 돔을 업데이트하는 과정을 거친다. 이 때 비교하여 실제 돔에 반영하는 과정을 reconciliation(조정)이라고 한다.
setCount(count + 1)를 하면 _val(closure의 상태값)을 변경한 것이지 내가 사용하는 count가 변경된 것은 아니다. 새로운 상태값을 확인하기 위해서는 리렌더링이 되어야 한다. Javascript는 싱글 스레드로 돌아가기 때문에 useEffect 실행이 끝난 이후에 리렌더링이 진행된다. (비동기) console.log()를 실행하는 시점은 리렌더링이 되기 전이므로 상태값이 0으로 나오는 것이다.
그렇다면 상태값을 1,000,000번 변경하면 1,000,000번 리렌더링 되는 것일까? 결론은 아니다! react는 퍼포먼스 향상을 위해 16ms동안 변경된 상태값들을 모아서 한 번에 리렌더링을 진행한다. 이것을 Batch Update라고 한다.
'Frontend > React' 카테고리의 다른 글
[React] Typescript 컴포넌트 타입 종류 (0) | 2024.07.21 |
---|---|
[React] Redux 상태 관리 라이브러리 (1) | 2024.07.03 |
[React] fetch()로 API 호출 (GET, POST, PUT, DELETE) (0) | 2024.06.23 |
[React] useEffect를 사용하여 mount/unmount/update시 작업 설정하기 (0) | 2024.06.22 |
[React] json-server (0) | 2024.06.21 |