브라우저에서 실행되는 js는 싱글스레드 언어이기 때문에 "한번에 하나의 작업"만 처리할 수 있다. 하지만 이런 환경속에서도, 실행 순서가 특정 타이밍에 적용되도록 보장해야할 순간이 생길 수 있다. 예를 들어, DOM 조작이 완료된 이후에 실행되어야할 코드이거나, react에서 상태 업데이트 이후 DOM에 반영된 후 처리되어여야할 작업들이다.
react의 상태 업데이트는 비동기적으로 처리된다. setState 코드를 만났다고 해서 그 즉시 DOM이 변경되는 것은 아니다. 이는 React 상태 업데이트의 기본 작동 방식이니 잘 알고 있을 테고...
하지만 만약 상태가 DOM에 반영된 이후에 실행되어야할 코드가 있다면? 실행 순서를 보장해야 할 필요가 있다. 그 방법은, 권장하진 않지만 아래와 같은 잡기술이 있다.
const CountTest: FC = () => {
const [count, setCount] = useState<number>(0)
const handleClick = () => {
setCount(count + 1);
// 상태 업데이트 이후 DOM 변경이 완료된 시점에 작업 실행
setTimeout(() => {
console.log(count);
}, 0);
};
return (
<>
<p>Count: {count}</p>
<button onClick={handleClick}>Increment</button>
</>
);
}
이렇게 하면, 변경된 state가 DOM에 반영된 이후에 console.log() 가 실행되도록 할 수 있다.
사실 개인적인 생각으론, 위의 방법은 현업에서 사용을 지양해야한다고 생각한다. 그 코드를 작성한 본인은 금방 이해할 수 있지만, 같이 리뷰하며 보는 동료 개발자는 음.. 이게 무슨 의도지? 하며 생각할 수 있을 것 같기 때문이다. 사실 위의 방법은 실무에 직접적으로 사용하기 보단 이벤트 루프의 작동 방식 이해에 좋은 예시 코드라 생각한다. 현업에선 위 방법을 대신하여 아래의 3개 방법을 추천한다.
useEffectuseEffect(() => {
console.log(`The updated count in DOM is ${count}`);
}, [count]); // count가 변경된 후 실행됨
flushSyncflushSync를 사용하면 상태 업데이트를 동기적으로 강제 실행할 수 있다. 하지만 이는 react의 기본적인 구조와 특징에 위배되는 기능이므로, react에서도 공식적으로 사용을 지양해달라곤 하고 있다.import { flushSync } from 'react-dom';
const handleClick = () => {
flushSync(() => {
setCount(count + 1);
});
console.log(`The updated count in DOM is ${count + 1}`);
};