혹시 리액트의 useState 훅을 이용하면서 스코프 안에 console.log로 찍은 값은 잘 나오는데,
setState로 처리된 결과값이 달랐던 경험이 있으신가요?
useState가 비동기로 작동된다.. useState의 Functional Updates 등 기본적인 동작원리는 다들 알고 계실겁니다.
// way 1
useEffect(() => {
let startOfMonth = moment(date).daysInMonth();
const monthDate = moment(date).format("YYYY-MM");
while (startOfMonth > 0) {
if (getItem !== null) {
setText((prev) => [
...prev, `Date_${monthDate}-${startOfMonth}`
]);
}
startOfMonth--;
}
}, [date]);
console.log(text);
// way 2
useEffect(() => {
let startOfMonth = moment(date).daysInMonth();
const monthDate = moment(date).format("YYYY-MM");
while (startOfMonth > 0) {
// 이 부분
let data = `Date_${monthDate}-${startOfMonth}`;
if (getItem !== null) {
setText((prev) => [...prev, data]);
}
startOfMonth--;
}
}, [date]);
console.log(text);
제가 원했던 결과값은 way 2번의 console 값이였습니다.
그러나 계속 way 1번과 같은 console값이 나오는 것입니다. 왜 이런 값이 나오는지 이해가 되지 않았습니다.
이미지를 보면 알겠지만 console에 찍히는 값은 0이 아니라 정상적으로 나옵니다.
뭐가 문제일까..? 그래서 혹시나 하고 setText에 있는 값을 변수로 만들고, console에 찍었더니 제대로 된 값이 나오는 겁니다.
처음엔 왜지...왜일까 고민의 끝은 실행(평가)되는 시점이였습니다.
console에 값은 정상적으로 나오지만 setText로 처리된 이후의 text의 값이 저렇게 나오는 것은 실행 시점과 관련이 있다고 생각이 들었습니다.
원인을 정리해보면
setText(prev => ...) 이렇게 인자를 넣게 되면 바깥에 있는 스코프와 실행시점이 다릅니다. (data 생성 시점)
setText의 인자로 실행되는 함수의 실행시점이 setText가 불려지는 시점과 다르다는 얘긴데 즉, setText가 비동기로 동작되면서 동기 코드인 while문의 평가가 끝나고 나서, setText 함수가 실행되게 됩니다.
way2번으로 작동이 잘 됐던 이유는 while문 블록 안에 let이 선언되어 있으므로 data는 항상 새로운 값을 만들고 있게 됩니다.
let data와 startOfMonth가 0이 아닐때 생성을 하게 되므로 메모리에 보관이 됩니다. setText가 비동기로 나중에 호출이 되지만 그전에 만들어둔 클로저를 통해 그 때 생성된 값이 전달되게 됩니다.
리액트의 useState를 사용하며 벌어진 일이지만 리액트는 JS로 만들어졌고 자바스크립트를 좀 더 깊게 이해하고 있었다면 고민의 결과가 좀 더 빨리 나와 해결할 수 있었을 것이라 생각이 듭니다.
// 참고 코드 (console 실행 순서)
const [count, setCount] = useState(0);
useEffect(() => {
let number = 0;
console.log("before setCount", number); // 2
setCount(() => {
console.log("inner setCount: number ?", number); // 4
return number + 1;
});
number = number - 1;
console.log("after setCount", number); // 3
}, []);
console.log(count, "countState); // 1