React에서 과도한 리(Re)-렌더링은 이제 그만 > 시티즌 인사이트

본문 바로가기

시티즌 커뮤니티

시티즌 인사이트

IT&개발 정보 React에서 과도한 리(Re)-렌더링은 이제 그만

페이지 정보

작성자 GrapeCity 작성일 21-04-06 11:34 조회 950회 댓글 0건

본문

광범위한 React 응용 프로그램에서 과도하거나 불필요한 렌더링은 일반적입니다. 이 문서에서는 React 응용 프로그램에서 과도한 리(Re)-렌더링 사례를 다루고 이를 방지할 수 있는 다양한 방법을 제공하고자 합니다. React 기초 사항과 React Hooks에 대한 기본적인 이해가 있는 독자를 대상으로 작성되었습니다.


일반적으로 React는 사용자 인터페이스(UI)를 업데이트하는 데 필요한 문서 객체 모델(DOM) 작업을 간소화하기 위한 기술을 사용하여 즉시 수행됩니다. 이 기술은 더 간단한 최적화 프로세스로 응용 프로그램 속도를 높이고 사용자 경험을 향상합니다.


React 응용 프로그램은 하나의 컴포넌트에서 완전한 기능을 제공하도록 빌드될 수 있습니다. 하지만 주요 단점은 단일 컴포넌트 기반 React 응용 프로그램이 각 상태 변경 시 다시 렌더링된다는 것입니다. 독립 실행형 React 응용 프로그램을 업데이트하려면 사용자가 중간 컴포넌트를 통해 상태를 전달해야 합니다.


이 방법을 사용하면 응용 프로그램 성능이 저하됩니다. 이 경우 단일 책임 원칙을 사용하여 컴포넌트를 분해하는 것이 가장 좋습니다. 이 원칙을 사용하면 각 컴포넌트는 가급적 하나의 함수를 유지합니다. 이 컴포넌트를 하위 컴포넌트로 분해하면 응용 프로그램 성능, 코드 재사용 가능성 및 상태 관리가 크게 향상됩니다.


단일 책임 원칙을 고수하면 많은 문제를 해결할 수 있습니다. 하지만 모든 문제가 해결되지는 않습니다. 상태 관리는 React 응용 프로그램에서 또 하나의 우려 요인입니다. Redux 및 MobX와 같은 상태 관리 라이브러리는 응용 프로그램의 크기와 복잡성이 증가함에 따라 개별 컴포넌트 내에서 상태를 관리하는 것이 번거롭기 때문에 React 응용 프로그램에 도입되었습니다. 주의할 점은 상태 컨테이너를 사용할 때 과도한 리(Re)-렌더링이 발생할 가능성이 있다는 것입니다.


모든 React 응용 프로그램에서 사용자가 응용 프로그램과 상호 작용할 때 UI를 업데이트하기 위해 상태 컨테이너에서 관리하는 응용 프로그램이 렌더링됩니다. 렌더링은 기본적으로 React가 DOM을 변경해야 하는지 여부를 알 수 있는 방법입니다. 초기 렌더링은 두 가지 기본 단계, 즉 렌더링 단계커밋 단계에서 발생합니다.


React에서 과도한 다시 렌더링은 이제 그만

React는 렌더링 단계에서 컴포넌트 트리의 루트부터 시작하여 하위 컴포넌트로 프로세스를 계속 진행합니다. 각 컴포넌트를 통과할 때 React는 createElement 메서드를 호출하고 컴포넌트의 JSX를 React 요소로 변환한 다음 해당 렌더링을 저장합니다. 이 변환이 완료되면 React 요소는 커밋 단계로 넘어가며, 이 단계에서 ReactDOM 패키지를 사용하여 DOM에 적용됩니다.


상태를 업데이트하기 위한 두 번째 또는 후속 렌더링을 일반적으로 리(Re)- 렌더링이라고 합니다. 리(Re)- 렌더링은 다음의 세 가지 상황에서 발생할 수 있습니다.

  1. 컴포넌트의 속성이 업데이트되는 경우

  2. 컴포넌트의 상태가 업데이트되는 경우

  3. 상위 컴포넌트의 렌더링 메서드가 호출되는 경우


불필요한 리(Re)-렌더링으로 응용 프로그램 성능이 저하되어 사용자의 배터리가 소모됩니다. 다행히 이러한 비효율성은 적절한 기술로 방지할 수 있습니다. 계속 읽어보고 리(Re)- 렌더링에 대한 자세한 내용과 과도한 리(Re)- 렌더링을 방지하는 방법에 대해 알아보세요.


리(Re)- 렌더링은 언제 발생하나요?

리(Re)-렌더링은 컴포넌트의 상태 또는 속성이 변경될 때 발생합니다. 이 두 가지가 변경되지 않으면 리(Re)-렌더링은 발생하지 않습니다. 초기 리(Re)-렌더링과 마찬가지로 리(Re)-렌더링도 렌더링 및 커밋 단계 프로세스를 따릅니다. 그러나 리(Re)-렌더링의 경우, React는 업데이트 플래그가 지정된 컴포넌트를 찾습니다.

플래그가 지정된 모든 컴포넌트에 대해 컴포넌트의 JSX가 createElement 메서드를 사용하여 React 요소로 변환되고 결과가 저장됩니다. 그런 다음 React는 주로 가상 DOM에 대한 React 요소의 이전 트리와 새 트리를 구분하는 조정을 거칩니다. React 문서에서 언급되었던 것처럼,

"React를 사용할 때 어느 시점에서 render() 함수를 React 요소의 트리를 만드는 것으로 생각할 수 있습니다. 다음 상태나 속성 업데이트 시 해당 render() 함수는 다른 React 요소 트리를 반환할 것입니다. 그런 다음 React는 UI를 최신 트리와 일치하도록 효율적으로 업데이트하는 방법을 알아내야 합니다."

조정 프로세스가 완료되면 결과는 커밋 단계로 전달된 다음 DOM에 적용됩니다. 렌더링이 발생할 때 JSX를 사용하여 어떤 일이 벌어지는지 더 자세히 알아보려면 블로그 게시물을 확인해 보세요.

실수로 새로운 속성 객체를 만들어 불필요한 리(Re)-렌더링 작업을 수행하게 될 수 있습니다. 이 방법이 어떻게 가능한지에 대해 계속 자세히 알아보면서, 먼저 다음 그림을 통해 렌더링이 낭비되는 원인을 살펴보겠습니다.

React에서 과도한 다시 렌더링은 이제 그만

사용자가 위의 C4에서 업데이트를 트리거하는 버튼을 클릭한다고 가정해 보겠습니다. 업데이트는 루트로부터 내려와 P3를 통과하여 필요한 변경을 수행하기 위해 C4로 이동합니다. 정상적인 React 환경에서 루트가 렌더링되면 모든 하위 컴포넌트가 자동으로 렌더링됩니다. 이 경우 P1과 P2는 하위 구성 요소와 마찬가지로 P3와 함께 자동으로 리(Re)-렌더링되어 변경 사항에 영향을 주지 않으므로 렌더링이 낭비됩니다. 이제 응용 프로그램 속도를 저하시키고 배터리 소모로 이어지는 원인을 보여주는 예를 볼 수 있습니다.

속성 전달의 좋은 예는 다음의 클래스 컴포넌트와 함께 잘 나타나 있습니다.

 class MovieBuzz extends Component {

render() {  
const movie = {  
name: "Deadpool",  
director: "Tim Miller",  
year: "2016",  
};  
return (  
<div>  
    <MovieDetails movie={movie} />  
    <MovieRating ratings={this.props.ratings} />  
</div>  
);  
}

이 예시에서는 MovieDetailsMovieRating의 두 가지 구성 요소가 렌더링됩니다. 그러나 새로운 MovieRating은 결과적으로 변경될 필요가 없는 MovieDetails가 리(Re)-렌더링되는 원인이 됩니다. 이 문제를 해결하려면 다음과 같이 코드를 다시 작성해야 합니다.

class MovieBuzz extends Component {  
  movie = {  
    name: "Deadpool",  
    director: "Tim Miller",  
    year: "2016",  
};  
render() {  
return (  
  <div>  
    <MovieDetails movie={this.movie} />  
    <MovieRating ratings={this.props.ratings} />  
  </div>  
);  
};

위와 동일한 클래스 컴포넌트를 다시 작성하면 MovieBuzz가 새 속성을 수신할 때 render 함수가 movie에 대한 새 상수를 생성하지 않음을 알 수 있습니다. 이렇게 하면 컴포넌트를 불필요하게 다시 렌더링할 가능성이 있는 새 속성 개체가 생성되는 것을 방지할 수 있습니다. 이 시나리오는 간단하지만, 이 기술이 더 많은 컴포넌트를 사용하여 훨씬 더 큰 응용 프로그램을 얼마나 간소화할 수 있는지 상상해 볼 수 있습니다. 렌더링은 DOM 업데이트와 다르다는 점을 기억하세요. 컴포넌트는 DOM의 변경 사항에 영향을 미치지 않으면서 렌더링될 수 있습니다.


컴포넌트 재사용

응용 프로그램에서 여러 번 사용할 수 있는 컴포넌트는 충분히 일반적이어야 합니다. 클래스 기반 컴포넌트와 달리, 후크가 있는 기능 컴포넌트는 명시적인 수명 주기 메서드 없이 수명 주기를 거칩니다. 이 프로세스를 통해 사용자 정의 후크에서 코드를 재사용할 수 있게 됩니다. 하지만 후크가 있는 순전히 기능적인 컴포넌트는 항상 React 렌더 주기 동안 리(Re)-렌더링된다는 점에 유의해야 합니다. 따라서 React Hooks는 컴포넌트 재사용에 대한 최상의 예를 제공합니다.

상위 컴포넌트가 렌더링되면 React는 모든 하위 컴포넌트를 재귀적으로 렌더링합니다. useState 후크를 사용하는 를 살펴보겠습니다.

일반적인 'counter' 및 'timer' 명명 규칙에서 벗어나려는 시도에서 이러한 버저의 예로는 LoudSoft 컴포넌트가 있습니다. Loud 컴포넌트는 Soft의 상위 컴포넌트입니다.

React에서 과도한 다시 렌더링은 이제 그만

https://codesandbox.io/s/beautiful-morning-g8y64?file=/src/Components/Loud.js

첫 번째 로드 시 페이지에는 의도한 대로 Buzzer 버튼 아래에 "Soft Buzzer"가 표시됩니다. 버튼을 클릭하면 상태 업데이트 메시지가 전송되어 Loud 컴포넌트가 다시 렌더링됩니다. 규칙에 따라 Soft 컴포넌트도 다시 렌더링됩니다. Soft 컴포넌트에서는 아무것도 변경되지 않았으므로 출력이 없습니다. 따라서 이것은 소프트(하위) 컴포넌트의 불필요한 렌더링입니다.

이 문제를 해결하는 쉬운 방법은 Loud 컴포넌트에서 Soft 컴포넌트를 제거하고 App.js에서 Soft 컴포넌트를 Loud 컴포넌트의 하위로 만드는 것입니다. 이 해결 방법은 아래에 설명되어 있습니다.

React에서 과도한 다시 렌더링은 이제 그만

불필요한 하위 렌더링을 해결하려면 속성에서 Soft 컴포넌트를 제거하고 JSX가 이 컴포넌트를 포함해야 합니다. 이 작업은 Soft 컴포넌트를 렌더링합니다. 이 변경 사항이 구현된 후에는 일반 콘솔에서 버저 버튼을 클릭하면 Loud 컴포넌트가 렌더링됩니다.

React에서 과도한 다시 렌더링은 이제 그만

React에서 과도한 다시 렌더링은 이제 그만

하위 컴포넌트가 렌더링 단계를 거치지만 커밋 단계를 거치지 않는 경우 불필요한 렌더링이 발생합니다. 이 문제를 해결하는 한 가지 방법은 위에 표시된 것처럼 정적이거나 자주 사용되지 않는 컴포넌트를 상위(또는 최상위 수준) 컴포넌트로 가져오는 것입니다.

React Hooks - Understanding Component Re-renders 문서에 자세히 설명된 대로 각 사용자 정의 후크는 실행 흐름에 각각 다르게 영향을 미칩니다.


리(Re)-렌더링 방지: 이전 방식

클래스 기반 컴포넌트의 과도한 리(Re)-렌더링을 방지하는 기존 방법은 shouldComponentUpdate 수명 주기 메서드를 사용하여 컴포넌트 업데이트를 재정의하는 것입니다. 이 메서드는 nextPropsnextState의 두 가지 매개 변수를 사용합니다. 기본적으로 이 메서드를 호출할 경우 컴포넌트는 속성이 변경되지 않았더라도 새 속성을 수신하면 다시 렌더링됩니다. 렌더 메서드가 호출되지 않도록 하려면 반환을 false로 설정하여 렌더링을 취소합니다. 이 메서드는 컴포넌트가 렌더링되기 전에 호출됩니다.

모양은 다음과 비슷합니다.

shouldshouldComponentUpdate(nextProps, nextState) {  
if (this.state.buzz !== nextState.buzz) {  
return true;  
}  
return false;  
}  

컴포넌트의 상태 또는 속성이 변경되더라도 리(Re)-렌더링을 방지하려는 경우가 있을 수 있습니다. 예를 들어 컴포넌트가 속성 또는 상태 내에 중첩된 데이터만 신경을 쓰고 특정 데이터가 변경되지 않은 경우입니다.


리(Re)-렌더링 방지: 새로운 방식

후크는 기존의 React 세계를 완전히 바꾸어 놓았습니다. 식상한 표현처럼 들릴 수 있지만, 말 그대로입니다. 후크가 React에 도입되어 변경된 중요한 사항 중 하나는 클래스를 제거할 수 있다는 것입니다. 그 후 React는 사용자 정의 후크가 있는 컴포넌트와 같은 기능 컴포넌트에서 더 나은 렌더링을 위해 React.memo를 도입했습니다. 이러한 도입은 과도한 리(Re)-렌더링을 방지할 수 있는 새로운 방법을 제공했습니다.

React.memo는 동일한 속성이 주어진 동일한 결과를 렌더링할 때 컴포넌트를 래핑하여 기능 컴포넌트의 성능을 최적화하는 고차 컴포넌트입니다. 이 컴포넌트는 렌더링 출력을 메모하여 성능을 향상합니다.

따라서 컴포넌트 속성이 렌더링 간에 변경되지 않으면 React는 건너뛰고 마지막으로 렌더링된 결과를 다시 사용합니다. 여기서는 새로운 방법이 기존 방법과 상충된다는 사실을 알고 있어야 합니다. React.memo를 사용할 때 shouldComponentUpdate가 후크 기반 컴포넌트에서 더 이상 작동하지 않아야 합니다.

응용 프로그램 성능을 향상하기 위해 두 가지 방법으로 React.memo를 구현하는 데, 단일 컴포넌트를 유일한 매개 변수로 사용하거나 두 번째 매개 변수로 추가된 비교 함수를 사용하는 방법입니다.

단일 컴포넌트를 유일한 매개 변수로 사용하여 React.memo 구현

우리는 앞에서 속성이 변경되지 않았는데도 React 컴포넌트가 어떻게 리(Re)-렌더링되는지 보았습니다. 예를 들어 상위 컴포넌트가 렌더링되면 하위 컴포넌트도 렌더링됩니다. 이 동작을 방지하려면 React.memo를 하위 컴포넌트 주위의 래퍼로 구현하고 필요한 가져오기를 확인해야 합니다. 이 방법을 사용하면 속성이 변경될 때만 하위 구성 요소가 다시 렌더링합니다.

위와 동일한 명명 규칙을 사용한 기본 사용 사례는 다음과 같습니다.

function SoftComponent({ buzz }){

return(

<div>

SoftComponent: { buzz }

</div>

);

두 번째 매개 변수로 추가된 비교 함수를 사용하여 React.memo 구현

여기에는 콜백 함수 속성 처리가 포함됩니다. 이 경우, 속성을 콜백으로 사용하는 컴포넌트를 메모할 때 각 렌더링마다 다른 결과가 나올 수 있으므로 특히 주의해야 합니다. 예를 들어,

function SoftComponent({ handleClick }) {

return (

<div onClick={handleClick}>

    SoftComponent

</div>

);

}  
const MemoizedSoftComponent = React.memo(SoftComponent);  
function LoudComponent() {

return (

<MemoizedSoftComponent handleClick={() => {}}/>

);

}

위의 예는 LoudComponent가 변경되었으므로 매번 다시 렌더링됩니다.

이 문제를 해결하기 위해 useCallback 함수를 사용하여 종속성 중 하나가 변경된 경우에만 변경된 콜백의 메모된 버전을 반환합니다.

function LoudComponent() {

const onHandleClick = useCallback(() => {  
    // returns the same function when re-rendered

});  
return (

<MemoizedSoftComponent

    handleClick={onHandleClick}/>);

}  


다음 단계

우리는 이제 리(Re)-렌더링이 어떻게 발생하는지, 무엇이 리(Re)-렌더링을 야기하는지, 그리고 불필요한 리(Re)-렌더링을 어떻게 줄일 수 있는지 알아보았습니다. 과도한 리(Re)-렌더링을 방지하기 위해 큰 비용이 드는 컴포넌트를 렌더링 빈도가 낮은 상위 컴포넌트로 이동한 후 속성으로 전달하세요. 그래도 충분하지 않으면, 더 나은 성능을 위해 코드에 React.memo를 삽입해 보세요.

이 주제에 대해 더 자세히 알아보려면 React 리(Re)-렌더링을 최적화하는 간단한 트릭 한 가지를 확인해 보세요.

  • 페이스북으로 공유
  • 트위터로  공유
  • 구글플러스로 공유
  • 카카오톡으로 보내기

댓글목록

등록된 댓글이 없습니다.

그레이프시티 홈페이지를 통해 제품에 대해서 더 자세히 알아 보세요!
홈페이지 바로가기

인기글

더보기
  • 인기 게시물이 없습니다.
그레이프시티 홈페이지를 통해 제품에 대해서 더 자세히 알아 보세요!
홈페이지 바로가기
이메일 : sales-kor@grapecity.com | 전화 : 1670-0583 | 경기도 안양시 동안구 시민대로 230, B-703(관양동, 아크로타워) 그레이프시티(주) 대표자 : 허경명 | 사업자등록번호 : 123-84-00981 | 통신판매업신고번호 : 2013-경기안양-00331 Copyright ⓒ 2021 GrapeCity inc.