리액트의 참조형 데이터 업데이트

Kimaramy·2020년 7월 5일
1

프론트엔드 개발

목록 보기
11/14

setState로 참조형 데이터 업데이트 하기

클래스 컴포넌트의 setState 는 이후 내부 render 메소드 실행 및 브라우저 렌더링 완료 과정까지를 동반한 함수이며, 이전 state를 비교해 값이 변경되어야 그 동작이 일어난다. 그래서 특히 참조형 데이터가 있을 때는 주의가 필요한데, 배열이 값으로 할당된 state를 업데이트하려 할 경우, 그 배열의 상태를 변경하지 않는 방법. 즉, 새로운 배열을 반환하는 방식을 사용해야한다. 예를 들어, push 메서드는 원본 배열을 변경하게 되므로 setState 는 이전 state와 비교했을 때, 변경점이 없는 것으로 판단해 해당 사항에 대해 다시 렌더를 실행하지 않는다. 그래서 새 배열을 반환하는 방법을 사용해야 하는데, 대표적으로 concat 또는 ... spread 연산자를 사용하는 방법이 있다.

예시

state = {
  str: '',
  arr: [], 
}

❌ DO NOT

mutable한 메소드를 사용하면, 상태 업데이트 및 렌더는 발생하지 않는다.

ex. push

this.setState({
  str: 'abc',
  arr: this.state.arr.push('React'),
  // this.state.arr는 []에서 ['React']로 변경이 이루어졌고,
  // 그 변경된 this.state.arr가 새 arr의 값으로 할당됐기 때문에
  // prevState === newState 가 되어 이 부분은 새 렌더링이 발생하지 않음
});

state 변화

oldnew
['React']['React']

✅ DO

immutable한 메소드를 사용해야 한다.

ex. concat

this.setState({
  str: 'abc',
  arr: this.state.arr.concat('React'),
  // concat은 새 배열을 반환하므로 this.state.arr는 불변함
});

state 변화

oldnew
[ ]['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],
  };
});

setState의 한계

얕은(shallow) 비교를 하기 때문에 1차원 객체(배열)에 대한 변경만 제대로 인지한다. 하지만 전후 state 변화가 일어나지 않음에도 렌더 함수를 실행시키는 오류가 일어날 수 있다. 그래서 PureComponent, React.memo, shouldComponentUpdate 등을 통해 이를 방지할 수 있다.

일반 setState와 함수형 setState 방식의 차이

일반 setState

일반적으로 사용하는 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이 된다. 왜냐하면 setStatethis.state.count 에서 값을 읽어 오는데, 리액트는 컴포넌트가 다시 렌더링될 때까지 count를 갱신하지 않기 때문이다. 그러므로 setState는 매번 count 값을 0으로 읽은 뒤에 이 값을 1로 설정한다.

함수형 setState

반대로, 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 출력

더 알아보기


profile
스타트업에서 Software Engineer로 일하고 있습니다

0개의 댓글