마이리얼트립 웹사이트 성능 측정 및 최적화 Part 2. 렌더링

여행 경험을 돕는 웹사이트 가꾸기

Sanghyeon Lee
How we build MyRealTrip
26 min readJun 10, 2019

--

지난 Part 1에서는 마이리얼트립 웹사이트에 필요한 리소스 로딩의 최적화 과정을 다뤘습니다.

이번 글에서는 웹페이지를 그리는 렌더링 과정의 성능 최적화에 초점을 맞추고자 합니다.

렌더링: 컴퓨터 프로그램을 사용하여 모델로부터 영상을 만들어내는 과정

렌더링의 정의를 웹사이트에 대입하면 브라우저가 HTML 문서로부터 사용자가 인지하고 상호작용할 수 있는 화면을 만들어내는 과정이 됩니다.

CSS는 시각을 통해 화면의 구조를 인지할 수 있도록 돕습니다. UI 요소의 위치와 여백, 크기, 색상 등을 지정합니다. 그리고 상호작용은 버튼을 누르거나 문자를 입력하는 등의 액션에 대한 피드백을 받는 과정을 뜻하며 이 역할은 자바스크립트가 주로 담당합니다.

이렇게 브라우저 렌더링에 관여하는 CSS와 자바스크립트를 최적화해 렌더링에 소요되는 시간과 횟수를 줄이는 것이 목표입니다.

Part 1의 링크들을 포함해 이번 개선 작업에는 React 개발자의 연예인 Dan Abramov님의 블로그와 React 공식 문서에서 큰 도움을 얻었습니다.

Table of Contents

  1. 속도의 기준
    1.1. 모바일 환경
    1.2. 프레임 속도
    1.3. 불필요한 렌더링
  2. 측정 방법
    2.1. CPU 성능 제한
    2.2. Profiling
    2.3. React Profiler
  3. 개선 과정
    3.1. 픽셀 파이프라인 최적화: Do Not Disturb
    3.2. 작업 시점 제어: 피할 수 없다면 미루자
    3.3. React 렌더링 최적화: Don’t Repeat Yourself
    3.4. React Hooks 최적화: 알아가는 사이
  4. 결론

1. 속도의 기준

1.1. 모바일 환경

브라우저 렌더링은 CPU 리소스를 점유합니다. 그리고 렌더링 최적화가 되지 않은 경우 불필요한 CPU 사용량이 늘어나 배터리 성능에 악영향을 미칩니다. 이는 야외 활동의 비중이 높은 여행자에게 좋지 않은 경험을 주게 됩니다.

렌더링 성능 측정 및 개선 작업은 CPU 성능이 낮은 모바일 환경을 기준으로 진행했습니다.

1.2. 프레임 속도

사람의 눈은 1초당 60개 이상의 프레임(60 fps, 프레임당 16.7ms)으로 이뤄진 애니메이션을 볼 때 움직임이 자연스럽다고 느낍니다. 반대로 60 fps를 초과할수록 움직임이 버벅인다는걸 느끼게 됩니다.

Chrome Devtools 애니메이션 예제

프레임을 그린다는게 꼭 애니메이션만을 대상으로 하지 않습니다. 호텔 검색을 눌러 목록을 불러오는 동안 버튼이 비활성화하거나 알림 버튼을 눌렀을 때 띄워지는 팝업 화면, 스크롤 시 화면의 이동 등 눈으로 인지하는 모든 피드백이 포함됩니다.

그렇다면 웹페이지의 각 프레임은 어떤 과정을 거쳐 그려질까요?

픽셀 파이프라인
  • Javascript: 가장 먼저 Part 1의 데모와 같이 자바스크립트로 스타일을 변경하는 구문이 있는지 확인한 뒤 해당되는 DOM 요소에 CSS class 또는 inline 스타일로 반영합니다.
  • Style: 현재 버전의 CSS를 어떤 DOM 요소에 적용해야 할지 계산합니다.
  • Layout: 각 요소의 너비나 위치를 갱신에 화면 상에 배치합니다.
  • Paint: 각 요소에 배경색, 글자 색과 같이 픽셀을 채우는 과정입니다.
  • Composite: 이전 과정에서 생성된 레이어를 병합합니다.

위 과정의 처리 시간이 16.7ms 을 초과하는 횟수가 늘어날수록 전체 렌더링 시간이 지연됩니다. 결국 앞서 나온 애니메이션 예제와 같이 여행자가 인지할만큼 반응이 느려지는 결과를 가져옵니다.

프레임 속도 비교

웹사이트에 머무는 동안 60 fps보다 낮아지는 구간을 찾아 개선해야 합니다.

1.3. 불필요한 렌더링

빠른 프레임 처리 속도보다 더 효과적인 것은 불필요한 프레임 발생을 줄이는 것입니다. 저희 팀은 React를 적극적으로 사용하고 있는데요, React는 상태의 변화에 따라 끊임없이 component를 렌더링합니다. 이 과정에서 일부 component의 렌더링이 불필요하게 발생할 수 있습니다.

정적인 정보(왼쪽)와 동적인 정보(오른쪽)

예를 들어 호텔 상세 페이지에서 날짜/인원 조건을 변경해 재검색하는 경우 조건에 맞는 객실 정보만 갱신하면 됩니다. 이 때 호텔명, 평점, 후기 수와 같이 검색 조건과 무관한 영역이 매번 영향을 받는다면 결과적으로 전체 렌더링 시간이 길어지게 됩니다.

각 component별로 갱신이 필요한 조건을 분석해 불필요한 렌더링을 제거해야 합니다.

2. 측정 방법

전반적인 브라우저의 렌더링 과정은 크롬 개발자 도구의 Performance 탭에서 측정이 가능합니다. 이번 편에서는 크롬 확장 도구인 React Developer Tools에서 제공되는 Profiler도 적극적으로 활용했습니다.

2.1. CPU 성능 제한

Performance 탭에는 제한된 CPU 성능을 시뮬레이션할 수 있도록 아래와 같은 옵션이 제공됩니다.

오른쪽 상단 Capture Setting 버튼을 누르면 CPU 성능 옵션 선택이 가능합니다.

일반적인 모바일 환경 재현을 위해 4x slowdown 항목을 선택했습니다. 그리고 지난 Part 1의 캐시 비활성화 옵션은 이번 작업에서도 계속 유지했습니다.

2.2. Profiling

측정 방식은 크게 두가지로 나뉩니다.

- 페이지 로딩 과정 전체 기록하기

빨간 박스의 버튼을 누르면 현재 페이지를 새로고침한 뒤 로딩이 마무리되는 시점까지 전 구간의 기록이 제공됩니다.

이 때 활용 가능한 실습으로 알아보는 프런트엔드 성능 최적화 교육에서 얻은 꿀팁이 있습니다. 혹시 아래 두 측정 결과의 차이점이 보이시나요?

하단의 빨간 박스에 해당 시점의 스크린샷이 보여지는데 위의 측정 결과는 기존 화면이 남아 있고, 아래는 빈 페이지에서 시작됩니다.

Elements 탭에서 body 태그를 직접 삭제한 뒤 기록을 시작하면 빈 페이지에서 측정을 시작해 더 정확한 결과를 얻을 수 있습니다.

- 측정 구간 선택하기

빨간 박스의 버튼을 눌러 기록 시작/중단을 제어할 수 있습니다. 페이지 상에서 특정 시점의 렌더링 성능을 집중적으로 측정할 때 유용합니다.

2.3. React Profiler

React component의 동작은 크게 두 단계로 구분됩니다.

  • Render: component에서 반환한 마크업을 이전 것과 비교해 변경 사항을 결정합니다.
  • Commit: Render 단계의 변경 사항을 DOM에 반영하고 life cycle method를 호출합니다. (Hooks 기반인 경우, useEffect 목록을 순차적으로 호출합니다.)

React Profiler를 통해 component별 render 소요 시간, 상태 등을 commit 단위로 상세히 확인할 수 있습니다.

또한, 각 component의 렌더링 시간 순으로 정렬해 볼 수도 있습니다.

3. 개선 과정

3.1. 픽셀 파이프라인 최적화: Do Not Disturb

DOM에 접근하는 자바스크립트와 각 CSS 속성은 픽셀 파이프라인의 layout과 paint 단계를 발생시킬 수 있습니다. 영향을 미치는 범위는 속성에 따라 다릅니다. 예를 들어 UI 요소의 너비나 높이를 변경하면 인접한 요소들의 위치도 재계산되어야 하기 때문에 layout을 발생시킵니다.

픽셀 파이프라인을 유발하는 속성 정보는 아래 링크에서 확인 가능합니다.

자바스크립트: https://gist.github.com/paulirish/5d52fb081b3570c81e3a
CSS:
https://csstriggers.com/

웹사이트를 이용하는 동안 화면을 그려내는 과정이 반복되는 것은 지극히 당연한 현상입니다. 하지만 아래와 같은 경우 렌더링 성능에 악영향을 줄 수 있습니다.

  • 브라우저가 열심히 스타일을 계산해 위치를 정하고 있는데 스타일 정보를 조회하거나 변경하는 경우 (강제 동기식 레이아웃, Forced Synchronous Layout)
  • 반복문과 같이 빠른 주기로 실행되는 코드에 픽셀 파이프라인을 유발하는 부분이 있는 경우 (레이아웃 스래싱, Layout Thrashing)

마이리얼트립 사이트에서 여행자와의 상호 작용이 많은 페이지 중 하나인 호텔 상세 페이지를 중심으로 측정했습니다.

프레임 성능 저하 구간

빨간 띠 영역은 강제 동기식 레이아웃, 실행 시간이 긴 작업으로 인해 프레임 속도가 저하되는 구간을 나타냅니다. 집중적으로 발생하는 영역을 확대하면 구체적인 원인을 파악해 볼 수 있습니다.

프레임 성능 저하 구간의 상세 정보

swiper.js라는 파일의 539번째 줄에서 강제 동기식 레이아웃이 발생했다는 정보를 제공합니다. 그리고 위쪽의 스택을 들여다보면 어떤 component의 componentDidMount life cycle의 initializeSwiper라는 메서드가 호출되면서 발생했다는 것을 파악할 수 있습니다.

만일 React 기반이고 React Profiler가 활성화되어 있다면 상단의 Timings 영역에서 어떤 component인지 확인 가능합니다. 이 경우 ImageSwiper라는 component에서 발생했습니다.

해당 박스를 클릭하면 하단의 탭에서 스타일 재계산으로 인해 726개 요소에 영향을 주었고, 약 757ms가 지연됐다는 정보를 추가로 얻을 수 있습니다.

swiper.js?d090:539 라는 파일 링크를 클릭하면 소스코드로 이동됩니다. 그리고 위와 같이 스타일 재계산을 발생시키는 getComputedStyle이 원인인 것을 확인할 수 있습니다.

이러한 과정으로 렌더링에 병목이 되는 지점을 추적하며 발견한 항목은 아래와 같습니다.

  • 호텔 소개 글의 ‘자세히 보기' 버튼 제공 여부 계산:
    getBoundingClientRect를 호출해 너비를 조회하는 코드로 인해 스타일 재계산이 발생합니다.
  • react-modal 라이브러리로 모달 창 열기:
    모달 내 콘텐츠의 focus 상태를 제어하는 코드가 포함되어 레이아웃을 발생시킵니다.
  • 달력 창을 띄울 때 이미 선택된 날짜가 있는 경우 해당 달로 자동 이동:
    스크롤 제어를 위해 scrollIntoView가 사용되고 있습니다.
  • Resize handler:
    반응형 레이아웃 구현을 위해 resize 이벤트로 브라우저 창의 크기 변화를 감지하고 있습니다. 이 때 window.innerWidth의 최초 1회 호출은 불가피하지만 하위 component에도 resize handler가 중복 적용된 경우 강제 동기식 레이아웃이 발생했습니다.

이렇게 강제 동기식 레이아웃이 발생하는 지점들은 파악했지만 기능 구현을 위해 꼭 필요하거나 외부 라이브러리 코드인 관계로 제거하는건 쉽지 않았습니다.

코드를 제거하지 않고 렌더링 성능을 저하시키지 않는 절충안이 필요했습니다.

3.2. 작업 시점 제어: 피할 수 없다면 미루자

원하는 시점에 코드를 실행하게 해주는 Web API가 두가지 있습니다.

- requestAnimationFrame

이 API는 주로 setTimeout의 동작 방식과 비교됩니다.

setTimeout API는 설정된 시간 동안 지연된 뒤 event loop에 밀려 있는 작업량에 관계없이 대기 작업으로 추가합니다. 이 때문에 렌더링과 관련된 코드가 적합하지 않은 시점에 실행될 수 있습니다. 반면 requestAnimationFrame API는 호출한 시점을 기준으로 다음 애니메이션 프레임이 시작될 때 전달된 콜백 함수가 실행되는 것을 보장합니다.

그 덕분에 특정 시간동안 지속적으로 프레임을 생성하는 경우 부드러운 애니메이션을 제공할 수 있습니다. 단, 콜백 함수의 실행 시간이 프레임 속도의 기준인 16ms를 넘어가지 않도록 유의해야 합니다.

이 API를 달력을 여는 시점의 스크롤 제어로 인해 강제 동기식 레이아웃이 발생하는 코드에 적용해봤습니다.

requestAnimationFrame 적용 전(왼쪽)과 후(오른쪽)

오른쪽 상단의 ‘Animation Frame Fired’ 영역으로 알 수 있듯이 클릭 이벤트에 포함된 scrollIntoView 호출 로직을 다음 프레임의 시작 시점으로 미루니 강제 레이아웃 동기화가 없어지고, 소요 시간, 영향을 주는 요소의 수가 현저히 줄었습니다.

- requestIdleCallback

requestIdleCallback API는 우선 순위가 낮은 작업을 콜백 함수로 전달해 브라우저 메인 스레드가 한가해지는 시점(idle 상태)에 호출합니다. 이를 통해 불필요한 프레임 지연을 줄여 렌더링을 개선할 수 있습니다.

대표적으로 웹사이트 활동을 추적해 수집하는 스크립트에 적용이 가능합니다. 호텔 상세 페이지에 진입하면 호텔 정보를 보여주는게 최우선이기 때문에 진입 이벤트 전송은 그 이후 해도 무방할 것입니다.

window.requestIdleCallback(() => {
sendEvent('viewHotel', { hotelId: 123 });
});

requestIdleCallback API 역시 프레임 기준 시간을 초과하지 않도록 작업 단위를 나눠서 실행하는게 좋고, 지원되지 않는 브라우저를 위해 shim을 적용할 수 있습니다.

참고로 2019년 2분기 출시 예정인 React Concurrent Mode도 이 API를 기반으로 우선 순위에 따라 작업을 잘게 쪼개 virtual call stack 상에서 관리한다고 합니다. 그리고 이런 처리를 하는 가장 큰 목적은 user interaction을 최우선으로 처리해 체감 속도를 향상시키기 위함입니다.

관련 링크
- 남다른 개선방법을 다시 보여준 페이스북의 React Fiber
- What is React Fiber?

3.3. React 렌더링 최적화: Don’t Repeat Yourself

React 공식 문서에도 잘 나와있듯이 불필요한 component 렌더링을 제거하기 위한 기법으로 PureComponent, shouldComponentUpdate 가 있지만 이 글에서는 추세에 맞게 주로 function 기반의 component를 기준으로 최적화 방안을 설명하고자 합니다.

React.memo

이 함수로 component를 감싸주면 이전 props와 현재 props의 각 필드를 비교(shallow comparison)해 업데이트 여부를 결정합니다. 하지만 이걸 사용한다고 해서 모든 component가 자동으로 최적화되지는 않기 때문에 상황에 맞춰 사용해야 합니다. 주로 props의 타입에 따라 적용 방식이 달라집니다.

Case 1. 원시 타입(string, number, boolean)으로만 구성된 경우

function HotelRoom(props) {
const { name, price, isFreeCancelable } = props;
return (
<section>
<h1>{name}</h1>
<p>{price}</p>
{isFreeCancelable && <p>무료 취소 가능</p>}
</section>
);
}
export default React.memo(HotelRoom);

각 prop의 비교 조건이 명확하고 재사용되기 좋은 component일 가능성이 높아 가급적 적용하는게 좋습니다.

Case 2. Function이 포함된 경우

function HotelRoom(props) {
const { name, price, onClick } = props;
return (
<section>
<h1>{name}</h1>
<p>{price}</p>
<button onClick={onClick}>선택</button>
</section>
);
}
export default React.memo(HotelRoom);

주로 onClick과 같은 이벤트 핸들러입니다. 이 경우 부모 component에서 전달되는 함수 prop이 매번 재생성되는지 확인해야 합니다. 만일 재생성된 함수가 전달되고 있다면 다른 prop들이 바뀌지 않았더라도 매번 렌더링을 수행하게 됩니다.

function Parant(props) {
return <Child onClick={() => { doSomething(); }} />;
}

이후 3.4. React Hooks 최적화 섹션에서 설명드릴 useCallback 또는 useReducer hook을 통해 해결 가능합니다.

Case 3. Array, Object가 포함된 경우

function Hotel(props) {
const { name, rooms } = props;
return (
<section>
<h1>{name}</h1>
{rooms.map(room => (
<li key={room.id}>{room.name}</li>
))}
</section>
)
}
export default React.memo(HotelRoom);

객체 타입의 경우 해당 props을 전달하는 부모 component에서 Array methods, Object.assign, spread operator 등으로 재생성하고 있지 않은지 확인해야 합니다. 재생성된 경우 참조가 바뀌기 때문에 React.memo를 적용하더라도 값의 변화와 무관하게 매번 렌더링됩니다.

function Parent(props) {
const { rawRooms } = props;
const rooms = [...rawRooms];
return <Child rooms={rooms} />;
}

순서 편집, 필드 맵핑 등으로 인해 객체의 재생성이 불가피한 경우 React.memo에서 제공되는 custom 함수로 비교 로직을 구성할 수 있습니다. 이런 경우를 위해 목록으로 구성된 각 객체에는 id와 같은 고유값이 존재하는게 좋고, 없다면 다른 필드들을 조합해 생성하는 것도 방법입니다.

function Hotel(props) {
const { id, rooms } = props;
return (
<section>
{rooms.map(room => <HotelRoom key={room.id} room={room} />)}
</section>
);
}
function areEqual(prevProps, nextProps) {
return prevProps.id === nextProps.id;
}
export default React.memo(Hotel, areEqual);

이제 rooms가 재생성된 배열이라도 호텔 ID 값이 변경된 경우에만 렌더링됩니다.

Case 4. children이 포함된 경우

children prop은 매 렌더링마다 참조가 바뀝니다. <Child />와 같은 React element는 React.createElement(Child)와 동일한 결과로 instance를 매번 생성하기 때문에 React element 간의 비교는 항상 false가 됩니다. 따라서 children prop이 포함된 component를 최적화하려면 아래 두 방법 중 하나를 적용해야 합니다.

1) children의 하위 component 대상으로 React.memo 적용하기
2) children prop 캐시하기

2)의 경우, class component라면 internal variable로 저장할 수 있고, function component라면 아래와 같이 useMemo hook을 활용할 수 있습니다.

function App(props) {
const { name, rooms } = props;
const children = useMemo(() => <HotelRooms rooms={rooms} />, [rooms]);
return (
<section>
<Hotel name={name}>{children}</Hotel>
</section>
);
}
function Hotel(props) {
const { name, children } = props;
return (
<section>
<h1>{name}</h1>
{children}
</section>
);
}
export default React.memo(Hotel);

3.4. React Hooks 최적화: 알아가는 사이

Concurrent Renering in React — Andrew Clark — React Conf 2018 발표 중 Hooks의 목적에 관한 내용

저희 팀은 React v16.8부터 지원되는 Hooks를 적극적으로 서비스에 도입하고 있습니다. 함수 기반의 간단한 문법으로 component를 작성하고, 상태 관리, 로직 재사용 등 거의 대부분의 기존 기능을 제공해 코드 자체보다는 UX에 관해 고민하는 시간을 늘리기 위함이라는 그 취지가 마이리얼트립의 방향성과 일치한다고 생각하기 때문입니다.

하지만 Hooks 자체가 아직은 초창기고 best practice를 찾아가는 단계라 비효율적인 코드를 작성하지 않기 위해 조심스럽게 적용할 필요가 있었습니다. 공식 문서의 FAQ에서 이런 부분을 많이 해소했습니다. 그 중 Hooks 최적화를 위한 몇 가지 방안을 공유드리고자 합니다.

- 함수 prop 최적화

3.3. React 렌더링 최적화 섹션의 두 번째 경우인 함수 prop 최적화를 위해 useCallback 또는 useReducer를 활용할 수 있습니다. useCallback을 활용하면 아래와 같이 참조가 변하지 않는 함수를 만들 수 있습니다.

function Parant(props) {
const { id } = props;
const onClick = useCallback(() => {
doSomething(id);
}, [id]);
return <Child onClick={onClick} />;
}

FAQ 문서에서는 이 방법보다는 useReducer를 통해 dispatch를 전달하는 것을 권장합니다. Redux 사용 경험이 있다면 익숙할 dispatch는 특정 액션을 호출해 상태를 변경하기 위한 함수이고, 한번 생성되면 참조가 변하지 않습니다. 따라서 context를 통해 dispatch 함수를 전역으로 전달한다면 매 component마다 useCallback으로 함수를 감싸는 공수를 줄일 수 있습니다.

- useEffect 한번만 실행하기

Hooks를 사용할 때 한번쯤 componentDidMount와 같은 코드를 작성하기 위해 두 번째 파라미터를 빈 배열로 전달한 경험이 있으실 겁니다. 이 때 effect 함수가 component의 prop, state와 같은 상태 값에 의존하고 있는지 점검해야 합니다.

function App(props) {
const { fetchHotel } = props;
function init() {
fetchHotel();
}
// OK: 의존하는 상태 값이 없음
useEffect(() => console.log('my effect'), []);
// Bad: fetchHotel prop이 변경되면 버그가 발생함
useEffect(() => fetchHotel(), []);

// Bad: init 내부에서 fetchHotel을 사용 중
useEffect(() => init(), []);
// rendering...
}

이 경우에도 함수 prop 최적화와 마찬가지로 useCallback으로 감싼 fetchHotel prop을 useEffect 의존성 배열로 전달해 해결 가능합니다.

function Parent(props) {
const { hotelId } = props;
const fetchHotel = useCallback(() => {
fetch(`/hotels/${hotelId}`).then(() => { ... });
}, [hotelId]);
return (
<App fetchHotel={fetchHotel} />
);
}
function App(props) {
const { fetchHotel } = props;
useEffect(() => fetchHotel(), [fetchHotel]); // rendering...
}

- 이벤트 구독/취소 effect 관리

저희 팀은 useWindowSize라는 custom hook으로 viewport 너비의 변화를 감지하고 있습니다. 이 hook은 내부적으로 resize 이벤트를 구독합니다. 문제는 component가 렌더링 될 때마다 cleanup 함수를 실행하기 때문에 렌더링 횟수만큼 불필요한 이벤트 구독/취소가 반복된다는 것입니다.

useReducer로 반환되는 dispatch 함수를 활용하면 component unmount 시점에만 cleanup이 되도록 처리할 수 있습니다.

function windowSizeReducer(state, action) {
if (action.type === 'resize') {
return action.width;
}
return state;
}
function useWindowSize() {
const [width, dispatch] = useReducer(windowSizeReducer, window.innerWidth);
useEffect(
() => {
function handleResize() {
dispatch({ type: 'resize', width: window.innerWidth });
}
const lazilyHandleResize = debounce(handleResize, 100); window.addEventListener('resize', lazilyHandleResize); return function detachResizeHandler() {
window.removeEventListener('resize', lazilyHandleResize);
};
},
[dispatch], // 의존성 배열로 dispatch 전달
);
return width;
}

dispatch는 custom hook을 사용하는 component가 unmount 될 때까지 참조가 변하지 않기 때문에 자연히 effect도 한번만 실행됩니다.

- DOM node 변경 감지하기

React에서는 DOM node 접근을 위해 ref를 주로 사용해왔습니다. Hooks 기반에서 ref로 할당한 node가 생성되는 시점에 특정 로직을 실행해야 할 경우, useRef hook을 사용하면 된다고 생각하기 쉬운데요, useRef는 변하지 않는 참조 객체를 반환하고, ref.current 값의 변화는 React component에서 감지되지 않기 때문에 node의 생성 시점을 판단하기 어렵습니다.

FAQ에 설명된 바와 같이 useCallback을 활용해 해결이 가능했고, intersectionObserver 기반의 custom hook에 적용했습니다.

useElementVisibility Custom Hook

line. 27 ~ 34의 subscribe 함수는 DOM node에 할당된 useRef 반환값과 동일한 기능을 하며 ref.current 값의 변화도 감지할 수 있습니다. 따라서 node가 생성되는 시점에 observer 감지 대상으로 등록이 가능합니다.

function App() {
const [visibility, subscribe, unsubscribe] = useElementVisibility();

return (
<main>
<ul>
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<footer id="bottom-observer" ref={subscribe}>When this area is visible, load the list further.</footer>
</main>
);
}

4. 결론

렌더링 최적화는 리소스 로딩에 비해 서비스의 속도나 성능 향상이 크게 체감되지는 않습니다. 하지만 로딩 이후 서비스를 사용하는 내내 사용자 경험에 영향을 미치는 부분이라는 점에서 중요도가 높습니다.

현재는 마이리얼트립이 다양한 애니메이션을 쓰거나 상호 작용이 많지 않기 때문에 아직 큰 문제가 되진 않지만 점차 동적인 사이트로 개선되어 갈 때 반드시 겪게 될 부분이라고 생각하며 Chrome Developer Tools, React Profiler 등과 같이 유용한 도구를 적극 활용해 지속적으로 해결해 나가고자 합니다.

Part 3에서는 메모리 누수가 발생하는 지점을 찾아 개선하는 과정을 다룰 예정입니다. 많은 관심 부탁드립니다.

아울러 마이리얼트립은 좋은 동료 분들을 계속해서 모시고 있습니다! 아래의 채용 페이지를 방문해주세요.

https://career.myrealtrip.com

Portions of this page are reproduced from work created and shared by Google and used according to terms described in the Creative Commons 3.0 Attribution License.

--

--