리액트의 클래스형 컴포넌트에서 상태 값을 업데이트할 때 사용되는 메서드인 setState
는 비동기적으로 동작합니다.
비동기적으로 setState
가 동작한다는 의미가 어떤 의미인지 다음 Counter
예제를 들며 설명해보겠습니다.
import React, { Component } from 'react';
class Counter extends Component {
state = {
number: 0,
fixedNumber: 0,
};
render() {
const { number } = this.state;
return (
<div>
<h1>{number}</h1>
<button
onClick={() => {
this.setState({ number: number + 1 });
this.setState({ number: number + 1 });
this.setState({ number: this.state.number + 1 });
}}
>
+1
</button>
</div>
);
}
}
export default Counter;
- 버튼을 누르면 1씩 증가한다.
위의 클래스형 컴포넌트에서 button
을 클릭하면, 상태 number
의 값이 3씩 증가할 것 이라고 우리는 예측합니다. 하지만 실제로는 1씩 증가합니다. 그 이유는 setState
가 비동기적으로 동작하기 때문입니다.
만약 부모와 자식이 click
이벤트가 발생해서 모두 상태를 변경하는 setState
를 호출했다고 합시다. 그러면 부모에서 상태가 변경되었고 자식에서도 상태가 변경되었으니, 상태가 총 2번 변경 되었습니다. 따라서 리액트는 해당 상태를 참조하여 렌더링하는 컴포넌트를 총 2번 렌더링해야합니다.
이는 리액트가 불필요한 렌더링이라고 판단하였습니다. 따라서 리액트는 브라우저 이벤트가 끝날 시점에 state를 일괄적으로 업데이트하여 한번만 렌더링하도록 성능을 개선시켰습니다.
즉, 리액트는 이벤트가 끝날 시점에 첫 번째, 두 번째, 세 번째 setState
는 비동기적으로 호출되고 상태를 일괄적으로 업데이트 합니다.
setState
가 비동기적으로 동작하여 위의 예제처럼 우리가 예상하는 대로 상태가 업데이트 되지 않았습니다. 그렇다면 number을 3 증가시키고 싶다면 어떻게 해야할까요?
다음과 같이 setState
에게 인수로 함수를 넘겨주면 됩니다.
this.setState((prevState, props) => ({
number: prevState.number + 1
}));
함수의 첫번재 인수인 prevState
는 기존 상태이고, props
는 현재 지니고 있는 props
를 가리킵니다. 하나의 이벤트에서 호출된 핸들러안에서 여러번 setState
를 호출하면, prevState
를 통해서 바로 직전에 업데이트된 상태를 참조할 수 있습니다.
위의 예시를 아래처럼 수정하면 button
을 클릭하였을 때 3씩 증가하는 것을 확인할 수 있습니다.
import React, { Component } from 'react';
class Counter extends Component {
state = {
number: 0,
fixedNumber: 0,
};
render() {
const { number } = this.state;
return (
<div>
<h1>{number}</h1>
<button
onClick={() => {
this.setState((prevState, props) => ({
number: prevState.number + 1,
}));
this.setState((prevState, props) => ({
number: prevState.number + 1,
}));
this.setState((prevState, props) => ({
number: prevState.number + 1,
}));
}}
>
+1
</button>
</div>
);
}
}
export default Counter;
- 버튼을 누르면 3씩 증가한다.
참조
리액트 공식문서