자바스크립트의 코드는 일반적으로는 동기식(synchronous)으로 처리된다.
즉 코드가 작성된 순서대로 윗줄부터 차례로 코드가 실행된다.
그래서 아래와 같은 코드는 순서대로 처리되어 출력 결과는 1, 2, 3 이 나오게 된다.
console.log('1');
console.log('2');
console.log('3');
그런데, 자바스크립트는 특정 함수들을 사용하게 되면 이를 비동기적(asynchronous)으로 처리한다.
만약 비동기 작업을 수행하는 코드를 만난다면, 이 코드의 출력 결과를 기다리지 않고 바로 다음 코드를 실행한다는 것이다.
예를 들면, 아래의 코드의 fetch는 비동기식으로 동작하는 API이다.
console.log('1');
fetch(url).then(data=>console.log('2'));
console.log('3');
처음으로 1을 출력하고, 바로 2가 출력되는 것이 아니라 fetch는 비동기적으로 처리되는 코드이기 때문
에 일단, 이를 어딘가에 따로 보관해두고 그다음 코드인 3을 출력한다.
그리고 나서 마지막으로 2가 출력된다.
두 가지 상태가 있다고 가정해보자
1. 버튼을 누른 횟수를 저장하는 count 상태
2. 같은 버튼을 누를때마다 키가 1cm씩 증가하는 height 상태
const [count, setCount] = useState(0);
const [height, setHeight] = useState(173);
function handleClick() {
setCount(count + 1);
setHeight(height+1);
}
버튼을 누를 때마다 당연히 둘이 똑같이 1씩 증가하는 코드이다.
그런데 사실, 위의 setCount(), setHeight() 같은 state 변경 함수들은 전부 asynchronous (비동기적) 으로 처리된다.
즉 위에서 비동기식 처리에 대해 설명했듯이 setCount(), setHeight()를 만나면 다음 코드부
터 실행한다는 것이다.
그렇기 때문에 상태 변경 함수를 동기식으로 처리된다고 인식하고 사용한다면 특정한 상황에서는 예상치 못한 버그가 생겨날 수 있다.
예를 들어 카운트가 3 이상이 된다면, height는 더 이상 증가하지 않도록 코드를 작성한다면, 아래와 같은 코드가 될 것이다.
function handleClick() {
setCount(count + 1); // 누를 때마다 count는 1씩 증가한다. 1 2 3 ...
if (count < 3) {
setHeight(height + 1); //174 175 이후 height의 값은 175에서 변경되지 않아야 한다.
}
}
작성한 코드대로 라면,
count가 1, 2, 3이 된다면, if 조건문에 따라서
height는 174, 175가 되고, 더 이상 증가하지 않고 175에서 멈춰야 한다.
그런데 count가 3이 됐을 때, hegiht는 176까지 증가하게 된다.
분명 count가 2일 때까지만 height +1을 수행하라는 코드이다.
count가 1일 때 height +1 (=174)
count가 2일 때 hegiht +1 (=175)
count가 3이면 height +1 는 더 이상 수행하지 않기.
그런데 count가 3일 때도 height +1를 해주고 있다.
이유는 위에서 언급한 비동기적인 특성 때문이다.
상태 변경 함수는 비동기적으로 처리되는 함수기 때문에 해당 함수를 만나면 잠깐 제쳐두고 다음 코드를 실행한다.
그래서 코드를 해석해 보자면
① 버튼을 세 번째 누르면 setCount(count+1); 이걸 실행해서 count를 3을 만들어준다.
② 근데 count를 3으로 만드는 건 비동기 코드니깐 잠깐 제쳐두고 if ( count > 3 ) {} 이걸 실행하게 된다.
③ 이때 count는 아직 2라서 if문 안의 setHeight(height+1)이 잘 동작하고 있는 것이다.
즉 setCount()가 비동기적으로 처리되는 함수라서 그렇다.
위의 코드를 예상대로 동기적으로, 순차적으로 실행하고 싶을 때 해결책은 useEffect이다.
또한, 해당 컴포넌트가 처음 렌더링 된 것인지 아닌지의 상태도 필요하다.
(사실, count 상태만으로도 해결이 가능하긴 하다. 이 코드에서 count가 0일 때는 처음 렌더링 된 것을 의미하니까!)
useEffect(()=>{
if(카운트가 3보다 작을 때 && 해당 컴포넌트가 처음으로 렌더링된 것이 아닐때){
setHeight(hegiht+1);
}
//...
},[count]) //count가 변경될 때마다 실행
function handleClick() {
setCount(count + 1);
}
function handleClick() {
setCount(prevCount => prevCount+1);
}