본 글은 React의 공식 홈페이지에서 state에 대해 설명한 부분을 참고하여 작성되었습니다.
더 자세하게 알고싶으신 분은 공식 홈페이지를 참고해주세요.
React는 DOM을 직접 조작하는 대신 Virtual DOM을 이용해 변경사항을 조작하므로 렌더링 성능이 우수하다고 알려져있습니다. 하지만 변경사항을 조작하는 것 자체가 느리다면 전체적으로 성능이 약화될 수 밖에 없을 것입니다. React는 컴포넌트의 state가 변경되면 렌더링을 다시하게 되는데, state의 변경이 한꺼번에 여러번 많이 발생하게 된다면 그 만큼 많은 렌더링이 많이 발생하게 될 것입니다. 이를 방지하기 위해 state의 변경사항을 즉시 반영하지 않고 변경사항을 대기열에 넣어 한꺼번에 적용시킵니다. 이 과정에서 setState
호출 후의 state 값이 개발자가 의도하지 않는 값이 될 수도 있습니다.
React에서 this.props
와 this.state
는 모두 렌더링된 값을 나타냅니다. 즉, 렌더링이 된 직후 현재 화면에 보이는 값이 저장되어 있는 것입니다. 하나의 setState
호출이 한 번의 컴포넌트 렌더링에 해당할까요? 아래의 예시를 살펴보죠.
import React, { Component } from 'react';
class App extends Component {
state = {
count: 0
}
add() {
this.setState({count: this.state.count+1});
}
handleClick() {
this.add();
this.add();
this.add();
}
render() {
return (
<div>
<p>{this.state.count}</p>
<button onClick={() => this.handleClick()}>Add</button>
</div>
);
}
}
export default App;
이 코드의 의도는 this.state.count
의 값을 3씩 증가시켜주는 것입니다. 하지만 실제로 위 코드는 3이 아닌 1씩 증가하게 됩니다. 왜 이런 현상이 발생하는 것일까요?
이것은 setState
의 호출이 비동기적으로 이뤄지기 때문입니다. setState
는 컴포넌트를 즉각적으로 갱신하지 않고 변경사항을 대기열에 집어넣어 여러 변경사항과 함께 일괄적으로 갱신합니다. 이를 위해 컴포넌트의 렌더링을 뒤로 미룰수도 있는 것이죠. 즉, setState
를 여러번 호출한다고 해도 한 번의 렌더링만 이루어질 수 있고, 따라서this.state
는 setState
에 의해 여러번 조작되는 동안 변경되지 않고 단 하나의 값만 가지게 됩니다.
setState
는 이벤트 핸들러 내에서 비동기적으로 동작합니다.
즉, 하나의 이벤트 핸들러 내에서 setState
가 여러번 호출된다면, 이벤트가 끝날 시점에 state를 일괄적으로 업데이트하고 렌더링합니다. 이는 더 큰 규모의 앱에서 뚜렷한 성능 향상을 만들어냅니다.
가장 좋은 방법은 setState
를 호출하자마자 this.state
에 접근하지 않는 것입니다. 하지만 상황이 여의치 않다면 componentDidUpdate 사용, setState Callback 사용 또는 setState의 updater 인자에 함수 사용하는 방법을 이용할 수 있습니다. 위 세가지 방법 모두 갱신이 이루어진 후에 실행되는 것이 보장되는 방법입니다.
add() {
this.setState({count: this.state.count+1});
console.log(this.state.count); // 갱신 전의 값이 출력
}
handleClick() {
this.add();
}
이 코드는 개발자가 의도한 대로 동작하지 않습니다. this.state
가 즉각 반영되지 않기 때문입니다. 아래의 세 가지 방법을 통해 위 문제를 해결할 수 있습니다.
// 1. componentDidUpdate 사용
componentDidUpdate() {
console.log(this.state.count);
}
add() {
this.setState({count: this.state.count+1});
}
handleClick() {
this.add();
}
// 2. setState Callback 사용
add() {
this.setState({count: this.state.count+1}, () => {
console.log(this.state.count);
});
}
handleClick() {
this.add();
}
// 3. setState의 updater 인자에 함수 사용
// count의 값이 3씩 증가한다.
add() {
this.setState((state, props) => {
return {count: state.count + 1}
});
}
handleClick() {
this.add();
this.add();
this.add();
}
3번째 방법인 setState의 updater 인자에 함수를 사용하는 경우, 함수 내부에서 state
의 값을 직접 조작해서는 안됩니다. 대신 인자로 전달된 state
와 props
를 기반으로 새로운 객체를 만들어 변경사항을 반환해야 합니다.
이전에 설명했듯이 모든 컴포넌트가 자신의 이벤트 핸들러 내에서 setState
를 호출할 때까지 React는 컴포넌트를 렌더링하지 않고 내부적으로 기다리고 있습니다. 이를 통해 불필요한 렌더링을 방지하면서 성능을 향샹시킵니다. 또, state를 동기적으로 업데이트하는 것은 props
와 state
사이의 일관성을 해칠 수 있고, 디버깅이 매우 어려운 이슈를 일으킬 수 있기 때문입니다.
더 자세한 예시와 설명은 이곳에서 확인할 수 있습니다.
좋은글 감사합니다! 이해가 됐어요!