React 렌더링 성능 최적화하는 방법

강형원·2022년 6월 14일
0

프론트엔드 면접

목록 보기
5/6

state 선언 위치

리액트는 특정 state가 변경되면 그 state가 선언된 컴포넌트와 그 하위 컴포넌트들을 모두 리렌더링 시킵니다. 따라서 state가 선언되는 위치를 잘 설계하는 것은 리렌더링 횟수에 엄청난 영향을 끼칩니다. 기본적으로 state의 선언위치는 이렇습니다. 해당 state를 사용하는 컴포넌트들을 잘 구분해놓은 뒤 그 컴포넌트들 중 가장 최상위 컴포넌트에 선언합니다. 만약 그 state를 사용하는 최상위 컴포넌트보다 더 상위 컴포넌트에 state를 선언하면 state를 사용하지 않는 더 많은 컴포넌트들이 state변경에 의해 불필요한 리렌더링을 겪게 됩니다.

예를 들어 다음과 같은 컴포넌트 구조가 있다고 합시다.
Index
ㄴGroup
ㄴUserList
ㄴUserItem

UserList와 UserItem에서만 사용되는 users state가 있습니다. 이 users state는 UserItem에서 보여줘야 할 데이터들을 가지고 있습니다. 이 데이터는 두 컴포넌트에서만 사용하기 때문에 그 중 가장 상위 컴포넌트인 UserList에 선언해야 합니다.

객체 타입의 state는 최대한 분할하여 선언합니다

객체가 크고 복잡한 구조인 경우 분할할 수 있는 만큼 최대한 분할하는 것이 좋습니다. 해당 state에서 일부의 프로퍼티만 사용하는 하위 컴포넌트가 있다면, 그 컴포넌트는 해당 프로퍼티가 변경될 때에만 리렌더링 되는 것이 바람직합니다. 만약 복잡한 객체로 선언된 state를 분할하지 않으면, 하위 컴포넌트가 사용하지 않는 다른 프로퍼티의 값이 업데이트될 때에도 리렌더링이 발생하므로 렌더링 최적화의 대상이 됩니다.

React.memo를 이용한 컴포넌트 메모이제이션 방법

React.memo는 컴포넌트를 래핑하여 props를 비교하여 리렌더링을 막을 수 있는 메모이제이션 기법을 제공하는 함수입니다. React.memo는 Hook이 아니기 떄문에 클래스형 컴포넌트에서도 사용할 수 있습니다. 함수형 컴포넌트에서는 shouldComponentUpdate를 사용할 수 없는데 리액트 공식 문서에서는 그 대안으로 React.memo를 제시하고 있습니다. React.memo는 콜백함수를 이용해 메모이제이션을 적용할지 여부를 판단할 수도 있습니다.

useMemo

만약 컴포넌트 내에 어떤 함수가 값을 리턴하는데 많은 시간을 소요한다면 이 컴포넌트가 리렌더링 될 때마다 함수가 호출되면서 많은 시간을 소요하게 될 것입니다. 그리고 그 함수가 반환하는 값을 하위 컴포넌트가 사용한다면 그 하위 컴포넌트는 매 함수호출마다 새로운 값을 받아 리렌더링할 것입니다. useMemo는 종속 변수들이 변하지 않으면 함수를 굳이 다시 호출하지 않고 이전에 반환한 참조값을 재사용합니다. 즉, 함수 호출 시간도 세이브할 수 있고 같은 값을 props로 받는 하위 컴포넌트의 리렌더링도 방지할 수 있습니다.

useCallback

useCallback도 같은 매커니즘으로 렌더링 최적화에 활용할 수 있습니다. 상위 컴포넌트에서 하위컴포넌트로 함수를 props로 넘겨줄 때 상위 컴포넌트가 리렌더링 될 때마다 상위 컴포넌트 안에 선언된 함수를 새로 생성하기 때문에 그때마다 새 참조 함수를 하위 컴포넌트로 넘겨주게 됩니다. 이에 따라 하위 컴포넌트도 props가 달라졌으므로 또다시 리렌더링 하게 되는 것이죠. 그러나 useCallback으로 함수를 선언해주면 종속 변수들이 변하지 않으면 굳이 함수를 재생성하지 않고 이전에 있던 참조 변수를 그대로 하위 컴포넌트에 props로 전달하여 하위 컴포넌트도 props가 변경되지 않았다고 인지하게 됩니다. 이에 따라 하위 컴포넌트의 리렌더링을 방지할 수 있습니다.

컴포넌트를 매핑할 때에는 key값으로 index를 사용하지 않습니다.

리액트에서 컴포넌트를 매핑할 때에는 반드시 고유 key를 부여하도록 강제하고 있습니다. 저는 얼마 전까지만 해도 key값으로 배열의 index값을 버릇처럼 넣었었는데 어느날 이게 얼마나 안좋은 습관인지 알게되었습니다. 어떤 배열에 중간에 어떤 요소가 삽입되면 그 중간보다 이후에 위치한 요소들은 전부 인덱스가 변경됩니다. 이로 인해 key값이 변경되고 리마운트가 일어나게 되죠. 또한, 데이터가 key와 매치가 안되어 서로 꼬이는 부작용도 발생합니다.

결론. 리액트 렌더링 최적화 기본 마인드

리액트는 단방향 하향식 데이터 흐름을 가지고 있습니다. 즉, 데이터는 부모 컴포넌트에서 자식 컴포넌트 방향으로 흘러갑니다. 이 데이터들(props, state)의 변화는 컴포넌트를 리렌더링시킵니다. state는 그것이 선언된 컴포넌트 내에서 사용되고, props는 부모 컴포넌트로부터 받은 데이터입니다.

따라서, 이미 만들어진 프로젝트에서의 렌더링 최적화는 첫째, state와 props의 변경을 최소화하는 것과 둘째, state와 props의 변경에 의해 불필요한 하위 컴포넌트 리렌더링을 최소화하는 것 두 가지 방향으로 진행됩니다.

이미 만들어지지 않은 프로젝트에서의 렌더링 최적화는 프로젝트 설계가 중요합니다. UI측면에서는 아토믹 디자인을 적극적으로 활용하여 컴포넌트 구조를 명확하고 직관적이고 최소화시키는 것이 좋습니다. 이것을 잘하면 컴포넌트 리렌더링 횟수는 획기적으로 줄일 수 있고 구조 자체가 명확하기 때문에 코드도 쉬워지고 유지보수성도 월등하게 좋아집니다. 데이터 측면에서는 state의 적절한 설계, API 설계가 중요하게 작용합니다. state에서는 UI에서 사용하기 편리한 데이터 구조를 선언하는 것이 좋습니다. API도 화면기획을 기반으로 의미론적으로 잘 분리된 형태로 쪼개서 만들어야 컴포넌트에서 API로 요청할 때, 불필요한 데이터를 응답데이터로 받지 않고 필요한 데이터만 적절하게 받아 리소스와 로직 낭비를 하지 않을 수 있습니다.

출처: https://cocoder16.tistory.com/36

profile
사람. 편하게.

0개의 댓글