setState는 왜 비동기적으로 동작할까

엄강우·2022년 6월 9일
0

TIL

목록 보기
33/43

다들 아시다시피 React에서 setState는 비동기적으로 동작합니다. 저도 너무 많은 state의 변화에 따라 리렌더링을 하면 성능에 이상이 생길 수 있기 때문에 setState를 모아서 한번에 처리해야하기 때문에 비동기적으로 해결 하는 것으로 알고 있습니다만 공식 문서를 통해 좀 더 자세히 알아보겠습니다.

주관적인 생각이 아닌 공식문서 issue에 올라온 글을 해석 및 정리 하였습니다.

출처

일괄적인 업데이트를 위해 재조정을 연기하는것이 이득이다.

즉, setState가 동기적으로 리렌더링되면 비효율적일 것이다. 업데이트가 여러번 있을 것으로 예상되면 일괄적으로 업데이트를 수행하는 것이 좋습니다.

예를 들면 click핸들러가 있을때 , childparent에서 모두 setState가 동작할 때 child가 2번 렌더링 되는 것을 원치 않습니다.

내부적 일관성 보장

state가 동기적으로 업데이트 되더라도 props는 그렇지 않습니다.

이제 리액트로 제공받은 객체는 각 각 내부적으로 일관되게 하고 싶습니다. 만약 그 객체들만 쓴다면 그것들은 꽉찬 재조정된 트리를 참고하게 될 것입니다.그럼 왜 이게 문제가 될까?

당신이 단지 state를 사용할때 이것이 동기적으로 동작하면 이 패턴은 이런식으로 동작할 것입니다.

console.log(this.state.value) // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value) // 2

하지만 state에 대해 얘기하면 몇 몇 컴포넌트들에 옮겨져야하고 공유되어야합니다 그래서 이것을 부모 컴포넌트로 옮겨야합니다.

하지만 이것은 우리의 코드를 어지럽게 합니다.

console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0
this.props.onIncrement();
console.log(this.props.value) // 0

목표하던 모델에서 코드가 망가지는 이유는 this.state 는 즉시 변경되지만 this.props는 그렇지 않습니다. 그리고 우리는 부모컴포넌트를 리렌더링 하지 않고는 this.props를 변경 시킬 수 없습니다. 이것은 우리가 일괄적인 것을 포기해야 한다는 것을 의미 합니다.

이런 예시가 완전히 이론적인 것을 커버하진 않습니다. 사실 Redux로 바인딩된 React가 정확히 이런 종류의 문제를 가지고 있었습니다. 왜냐하면 그들은 React Props와 non-React state가 섞여있기 때문입니다.

그래서 리액트는 이 문제를 this.statethis.props를 재조정과 플러쉬 이후에 업데이트 하였습니다. 그래서 당신은 리팩토링 전후에 0이 인쇄되는 것을 볼 수 있을 것입니다, 이것이 state를 안전하게 전달 해줍니다.

요약하면 React 모델은 항상 가장 간결한 코드를 따를 순 없습니다. 하지만 이것은 내부적 일관성과 state를 전달하는 것에 안전성을 보장합니다.

비동기 렌더링

설계적으로 리액트는 컴포넌트 단위로 각각 업데이트 되도록 동작합니다. 우리가 this.state의 업데이트를 즉시 적용하냐 안하냐는 이 때문에 논의할게 없었습니다. 왜냐하면 state의 업데이트가 순차적으로 이루어져야 해야만 했기 때문입니다.

우리가 리액트는 어느 컴포넌트로 부터 setState() 가 불러와지는지에 따라 다른 우선순위를 가지는것을 가지고 비동기 렌더링에 대해 설명하고자 합니다.

예를 들면 우리가 메시지를 타이핑 하고 있을때, setState()TextBox컴포넌트에서 즉시 적용 되어야합니다. 하지만 새로운 메시지를 보내는동안 당신은 타이핑을 하고 있습니다. 새로운 MessageBubble에 적절한 역치를 주어서 렌더링을 딜레이 시키는 것이 타이핑에 쓰레드가 막혀 더져지는것 보다 낫습니다.

우리가 어떤 업데이트에 낮은 우선순위를 준다하면 우리는 그것들의 렌더링을 아주 작은 덩어리 그리고 유저들이 눈치채지 못하게 아주 짧은 시차로 나눌 수 있습니다.

하지만 비동기 렌더링은 성능 최적화만을 위한것은 아닙니다. 우리는 리액트 컴포넌트 모델이 할 수 있는 것에 대한 근본적인 변화라고 생각합니다.

예를들어 당신이 한 화면에서 다른 화면으로 넘어가는 것을 생각해보세요. 당신은 새로운 스크린이 렌더링 될 때 까지 spinner를 보여줄 것입니다.

하지만 화면 전환이 충분히 빠르다면 spinner는 깜빡 거리며 지나갈 것이고 이것은 유저의 이용 경험을 낮추는 결과를 초래할 것입니다. 더 안좋게는 당신이 수개의 단계의 컴포넌트를 가지고 있고 각 각 다른 비동기 의존성을 가지고 있다면, spinner가 각 각 깜빡이며 지나갈 것입니다. 이것은 보기 안 좋을 뿐만 아니라 잦은 dom reflow로 인해 앱을 느리게 할것입니다.

간단한 setState()를 동작 했을대 다른 뷰가 렌더링 된다면 얼마나 좋을까? 업데이트된 화면을 배후에서 렌더링을 시작할 수 있다면 직접 어떤 코드도 작성하지 않고 너는 어떤 역치를 넘어서는 업데이트에는 spinner를 보여줄 수 있고 모든 새로운 서브트리의 비동기 의존성을 만족할 때 리액트가 완전한 전환을 수행하도록 합니다.

이것은 this.state가 즉시 적용되지 않지 때문에 가능합니다. 즉시 적용된다면 예전 화면이 여전히 보이고 상호작용가능한 상태에서 배우에 새로운 화면을 렌더링 시작하는 방법은 존재하지 않습니다.

정리

  1. 일괄적인 업데이트를 위해서 setState를 비동기적으로 동작하게하여 하여 재조정을 미루어 한번에 하는것이 좋습니다.

  2. 내부적으로 전달되는 props와 state의 일관성을 유지하기 위해서는 setState가 비동기적으로 동작해야한다.

  3. 비동기 렌더링을 하기위해서 setState는 비동기 적으로 동작해야한다.

profile
안녕하세요 프론트엔드 개발자를 꿈꾸는 엄강우입니다.

0개의 댓글