import { useState } from "react";
function App() {
const [count, setCount] = useState(0);
function handleClick() {
// 첫 번째 상태 업데이트
setCount(count + 1);
console.log(count); // 이 시점에서 상태는 아직 업데이트되지 않았습니다.
// 두 번째 상태 업데이트
setCount(count + 1);
console.log(count); // 이 시점에서도 상태는 아직 업데이트되지 않았습니다.
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increase</button>
</div>
);
}
export default App;
위와 같은 코드를 실행할 경우
count는 업데이트 이전의 값인 0으로 출력된다.
-> handleClick() 함수 안에서 출력되는 count 값은 업데이트 이전의 값이기 때문에 0으로 출력되기 때문이다.
또한, 리액트는 이벤트 핸들러 안에서 발생하는 모든 상태 변경을 한꺼번에 처리하기 때문에 화면에 반영되기 전까지 변경된 상태를 볼 수 없다.
이러한 특성은 성능 최적화를 위해 사용되며 리액트가 불필요한 렌더링을 줄일 수 있도록 도와준다.
렌더링을 하기 위해선 state의 변경이 수행되어야 한다.
state 하나가 변경될 때마다 화면에 반영한다면 매우 많은 'layout-shift'가 일어난다.
-> 리액트는 이러한 상황에서 발생하는 모든 상태 변경을 한 번에 수집하여 단일 렌더링으로 처리한다!
프로세스
state 변경(상태 변경) -> virtual DOM 생성 -> diffing -> reconciliation
virtual DOM
실제 DOM과 내용을 공유하는 복사본
diffing
렌더링 이전 버전의 가상돔과 업데이트 이후 변경될 가상돔을 비교한다.
(리액트는 항상 이 두가지의 가상돔을 함께 갖는다.)
reconciliation
가상돔에서 변경된 사항을 실제돔에 한꺼번에 반영한다. => 배치 업데이트
배치 업데이트를 할 경우 비동기적으로 업데이트 되기 때문에 상태가 즉시 업데이트 되지 않는다는 문제점이 있다.
배치 업데이트의 문제점 해결 방법
useEffect + 함수형 업데이트
(1) useEffect
useEffect(() => {
console.log("useEffect");
console.log(count);
}, [count]);
바로 반영되지 않았던 문제 해결
(2) 함수형 업데이트
function handleClick() {
// 첫 번째 상태 업데이트
setCount((prev) => prev + 1);
console.log(count); // 이 시점에서 상태는 아직 업데이트되지 않았습니다.
// 두 번째 상태 업데이트
setCount((prev) => prev + 1);
console.log(count); // 이 시점에서도 상태는 아직 업데이트되지 않았습니다.
}
배치 업데이트로 인해 setState 하나만 반영되었던 문제 해결
setState(콜백함수)
의 형태로 사용하면 반환값이 바로 반영되어 다음 setState에서 변경된 값을 받을 수 있다.
import { useEffect, useState } from "react";
function App() {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("useEffect");
console.log(count);
}, [count]);
function handleClick() {
// 첫 번째 상태 업데이트
setCount((prev) => prev + 1);
console.log(count); // 이 시점에서 상태는 아직 업데이트되지 않았습니다.
// 두 번째 상태 업데이트
setCount((prev) => prev + 1);
console.log(count); // 이 시점에서도 상태는 아직 업데이트되지 않았습니다.
}
return (
<div>
<p>Count: {count}</p>
<button onClick={handleClick}>Increase</button>
</div>
);
}
export default App;