클래스 컴포넌트의 setState
는 이후 내부 render
메소드 실행 및 브라우저 렌더링 완료 과정까지를 동반한 함수이며, 이전 state를 비교해 값이 변경되어야 그 동작이 일어난다. 그래서 특히 참조형 데이터가 있을 때는 주의가 필요한데, 배열이 값으로 할당된 state를 업데이트하려 할 경우, 그 배열의 상태를 변경하지 않는 방법. 즉, 새로운 배열을 반환하는 방식을 사용해야한다. 예를 들어, push
메서드는 원본 배열을 변경하게 되므로 setState
는 이전 state와 비교했을 때, 변경점이 없는 것으로 판단해 해당 사항에 대해 다시 렌더를 실행하지 않는다. 그래서 새 배열을 반환하는 방법을 사용해야 하는데, 대표적으로 concat
또는 ...
spread 연산자를 사용하는 방법이 있다.
예시
state = {
str: '',
arr: [],
}
mutable한 메소드를 사용하면, 상태 업데이트 및 렌더는 발생하지 않는다.
ex. push
this.setState({
str: 'abc',
arr: this.state.arr.push('React'),
// this.state.arr는 []에서 ['React']로 변경이 이루어졌고,
// 그 변경된 this.state.arr가 새 arr의 값으로 할당됐기 때문에
// prevState === newState 가 되어 이 부분은 새 렌더링이 발생하지 않음
});
state 변화
old | new |
---|---|
['React'] | ['React'] |
immutable한 메소드를 사용해야 한다.
ex. concat
this.setState({
str: 'abc',
arr: this.state.arr.concat('React'),
// concat은 새 배열을 반환하므로 this.state.arr는 불변함
});
state 변화
old | new |
---|---|
[ ] | ['React'] |
다른 방법
ex. concat
this.setState({
str: 'abc',
arr: [].concat(this.state.arr, newValue),
});
ex. concat
(callback setState)
this.setState(prevState => {
return {
str: 'abc',
arr: prevState.arr.concat(newValue),
};
});
ex. ...
spread 연산자 사용
this.setState({
str: 'abc',
arr: [...this.state.arr, newValue],
});
ex. ...
spread 연산자 사용 (callback setState)
this.setState(prevState => {
return {
str: 'abc',
arr: [...prevState.arr, newValue],
};
});
얕은(shallow) 비교를 하기 때문에 1차원 객체(배열)에 대한 변경만 제대로 인지한다. 하지만 전후 state 변화가 일어나지 않음에도 렌더 함수를 실행시키는 오류가 일어날 수 있다. 그래서 PureComponent
, React.memo
, shouldComponentUpdate
등을 통해 이를 방지할 수 있다.
일반적으로 사용하는 setState(object)
는 새로운 객체를 this.state
에 재할당하는 방식이다. 단, 동시 호출 호출을 보장하지 않으며 성능 향상을 위해 연속적인 setState 호출은 일괄 처리하게 된다.
state = { count: 0 }
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
this.setState({ count: this.state.count + 1 });
console.log(this.state.count); // 1 출력
컴포넌트가 다시 렌더링되는 시점에 count는 3이 될 것 같은 예상과 달리 1이 된다. 왜냐하면 setState
가 this.state.count
에서 값을 읽어 오는데, 리액트는 컴포넌트가 다시 렌더링될 때까지 count를 갱신하지 않기 때문이다. 그러므로 setState
는 매번 count 값을 0으로 읽은 뒤에 이 값을 1로 설정한다.
반대로, setState(function)
방식은 this.state
의 값을 내부 대기열(인자)에 담아 참조한다. callback은 이전 상태 값을 참조해 다음 상태 값을 변경하는 함수이며, 이전의 상태를 다 처리하면 다음 처리의 참조(비교) 대상이 된다. 그래서 연속적인 setState
상황에서 동기적으로 작동한다.
state = { count: 0 }
this.setState(prevState => { count: prevState.count + 1});
this.setState(prevState => { count: prevState.count + 1});
this.setState(prevState => { count: prevState.count + 1});
console.log(this.state.count); // 3 출력