본문 바로가기

FrontEnd

프론트엔드 성능 최적화 가이드 스터디 4장

반응형

프론트엔드 성능 최적화 가이드의 4장을 스터디한 내용입니다

 

프론트엔드 성능 최적화 가이드

웹이 가벼워지면 사용자의 만족도는 올라간다 웹사이트를 방문했는데 페이지가 늦게 로드되면 금세 ‘뒤로 가기’ 버튼에 손이 갑니다. 구글의 연구에 따르면 페이지 표시 시간이 1초에서 3초

blog.insightbook.co.kr

이전 시리즈

2022.11.15 - [웹성능최적화] - 프론트엔드 성능 최적화 가이드 1장 스터디

 

프론트엔드 성능 최적화 가이드 1장 스터디

프론트엔드 성능 최적화 가이드의 1장을 기반으로 진행한 스터디 입니다. 프론트엔드 성능 최적화 가이드 웹이 가벼워지면 사용자의 만족도는 올라간다 웹사이트를 방문했는데 페이지가 늦게

itchallenger.tistory.com

2022.11.15 - [웹성능최적화] - 프론트엔드 성능 최적화 가이드 2장 스터디

 

프론트엔드 성능 최적화 가이드 2장 스터디

프론트엔드 성능 최적화 가이드 2장을 기반으로 진행한 스터디 입니다. 프론트엔드 성능 최적화 가이드 웹이 가벼워지면 사용자의 만족도는 올라간다 웹사이트를 방문했는데 페이지가 늦게 로

itchallenger.tistory.com

2022.11.17 - [웹성능최적화] - 프론트엔드 성능 최적화 가이드 3장 스터디

 

프론트엔드 성능 최적화 가이드 3장 스터디

해당 도서의 3장을 기반으로 진행한 스터디 입니다. 프론트엔드 성능 최적화 가이드 웹이 가벼워지면 사용자의 만족도는 올라간다 웹사이트를 방문했는데 페이지가 늦게 로드되면 금세 ‘뒤로

itchallenger.tistory.com


이미지 갤러리 최적화

  • 해당 서비스는 다양한 주제의 이미지를 격자 형태로 보여줌
  • 헤더에는 Random, Animals, Food, Fashion, Travel이라는 버튼이 있음
    • 해당 버튼을 클릭하면 그에 속하는 이미지를 필터링해 볼 수 있음

이미지 갤러리 서비스 화면

아래 이미지 중 하나를 클릭하면 클릭한 이미지가 큰 화면으로 나타남

배경 색은 이미지 색상과 비슷한 색으로 맞춰짐

이미지 갤러리의 이미지 모달 화면

모달이 뜨는 절차

  • 사용자는 이미지를 클릭함
    • 이미지가 클릭되면 리덕스에 SHOW_MODAL 액션을 보냄
    • modalvisible 값을 true로 변경 (렌더링)
  • ImageModalContainer에서 modalVisible 값을 구독.
    • src, alt를 ImageModal 컴포넌트에 전달하고 모달을 띄움
  • 이미지가 완전히 로드되면(onLoad) getAverageColorOfImage를 통해 이미지의 평균 색상을 구하고 해당 값을 리덕스에 저장함
  • 다시 리덕스 스토어의 상태가 변하고, 최종적으로 변경된 bgColor를 ImageModal에 전달 (렌더링)

이 장에서 학습할 최적화 기법

  • 이미지 지연 로딩
    • 이전에는 Intersection Observer API를 사용하였음
    • 이번에는 react-lazyload 라이브러리를 사용하여 컴포넌트 자체를 지연 렌더링
  • 레이아웃 이동 피하기
    • 레이아웃 이동(Layout Shift)란 화면상 요소 변화로 레이아웃이 갑자기 밀리는 현상
    • 이미지 로딩 과정에서 레이아웃 이동이 많이 발생
    • 레이아웃 이동은 사용자 경험에 좋지 않은 영향을 줌
  • 리덕스 렌더링 최적화
    • useSelector와 equal함수 잘 이용하기
    • 파생 상태 리덕스 스토어에서 뽑아내기
  • 병목 코드 최적화
    • 로직 개선
    • 메모이제이션

분석 툴 소개

React Developer Tools(Profiler)

  • 얼마만큼의 렌더링이 발생하였는지 보여줌
  • 어떤 컴포넌트가 렌더링되었는지 보여줌
  • 해당 컴포넌트의 렌더링에 얼마나 시간이 소요되었는지 보여줌

React Developer Tools의 Profiler 패널

데이터

데이터에는 섬네일과 커다란 사진이 존재함

DB에 저장된 데이터


레이아웃 이동 피하기

  • 레이아웃 이동이란 화면 상의 요소 변화로 레이아웃이 갑자기 밀리는 현상
    • 아래 이미지보다 위 이미지가 나중에 로드될 경우 아래 이미지를 밀어내면서 그려짐

이미지 갤러리 에서 발생하는 레이아웃 이동

  • 레이아웃 이동은 사용자의 주의를 산만하게 만들고 의도하지 않은 클릭을 유발할 수 있음
    • 사용자 경험에 좋지 않은 영향

Lighthouse는 CLS(Cumulative Layout Shift) 항목을 두고 레이아웃 이동을 성능 점수에 포함함

Light house 검사 결과 중 CLS

  • 0이면 좋음(발생 x)
  • 1이면 나쁨

직접적인 원인 파악을 위해 Performance 패널을 확인

  • Experience 섹션을 보면 Layout Shift라는 빨간 막대가 표시됨
    • 해당 시간에 레이아웃 이동이 발생하였다는 의미임.
    • 해당 막대에 커서를 올리면 레이아웃 이동을 유발한 요소를 보여줌

Performance 패널에 표시된 레이아웃 이동 정보

레이아웃 이동의 원인

가장 흔한 경우

  • 사이즈가 미리 정의되지 않은 이미지 요소
    • 브라우저는 이미지를 다운로드하기 전까지 사이즈를 알 수 없음
      • 미리 영역을 확보할 수 없음
    • 이미지가 화면에 표시되기 전까지 해당 영역의 높이,너비는 0임
    • 이미지가 로드되면 높이가 해당 이미지의 높이로 변경되면서 그만큼 다른 요소들을 밀어냄
  • 사리즈가 미리 정의되지 않은 광고 요소
    • 이미지 요소와 원인 동일(이미지 + 영상)
  • 동적으로 삽입된 콘텐츠
    • 새로운 요소가 추가되면서 다른 요소를 밀어냄
  • 웹 폰트(FOIT, FOUT)
    • 폰트에 따라 글자 크기가 조금씩 달라 다른 요소의 위치에 영향을 줌

레이아웃 이동 해결

  • 레이아웃 이동을 일으키는 요소의 사이즈를 미리 지정
    • 즉, 해당 요소의 사이즈를 미리 예측할 수 있다면, 또는 미리 알고 있다면 해당 사이즈만큼 공간을 확보해둠
  • 하지만 이미지 갤러리의 이미지 사이즈는 브라우저의 가로 사이즈에 따라 변함
    • 따라서 단순히 너비와 높이를 고정하는 것이 아니라, 이미지의 너비 높이 비율로 공간을 잡아줌
    • 이번 예제에서는 16:9

Elements패널에서 확인한 이미지 비율 정보

 

이미지 크기를 비율로 설정하는 방법

1. padding을 이용하여 박스를 만들고 그 안에 이미지를 absolute로 띄우기

  • 아래와 같이 하면 wrapper의 너비인 160px의 56.25%만큼 상단 여백(padding-top)이 설정됨
    •  즉 너비는 160px, 높이는 90px이 됨
    • 이 상태에서 이미지를 absoulte로 넣어주면 부모 요소의 div와 사이즈가 동일하게 맞춰짐
    • 1대1 비율을 원하면 padding-top을 100% 넣어주면 됨
  • 퍼센트를 매번 계산해야 함
  • 56.25%는 직관적이지 않음
<div class="wrapper">
  <img class="image" src="..." />
</div>
<style>
  .wrapper {
    position: relative;
    width: 160px;
    padding-top: 56.25%; /* 16:9 비율 */
  }
  .image {
    position: absolute;
    width: 100%;
    height: 100%;
    top: 0;
    left: 0;
  }
</style>

2. aspect-ratio CSS 속성 사용

위의 퍼센트 계산, 비직관적 문제 해결

.wrapper {
  width: 100%;
  aspect-ratio: 16/9;
}
.image {
  width: 100%;
  height: 100%;
}
  • 자식 요소에 absolute 적용 필요도 없음
  • 코드가 간단함
  • 대신 호환성을 조심해야 함
aspect-ratio 속성의 호환성 표

실습 코드 : https://github.com/i0boy/frontend-performance-optimization-4/commit/ab26893a872c679acd4769e85db51f3dbc9f4893


이미지 지연 로딩

  • react-lazyload 라이브러리 사용
npm install --save react-lazyload
 
 
  • 지연 로드하고자 하는 컴포넌트를 감싸주기만 하면 됨
  • 해당 컴포넌트의 자식 요소들은 화면에 표시되기 전까지 렌더링 되지 않음
    • 이미지 뿐만 아니라 일반 컴포넌트 포함
  • 이미지가 크면 뷰포트에 들어올 때 로딩하는데 오래 걸릴 수 있음
    • offset을 이용하여 미리 당겨올 수 있음

실습 코드 : https://github.com/i0boy/frontend-performance-optimization-4/commit/5a735692bb2bafbadcd609f1f8c5e3bbb8f86eef


리덕스 렌더링 최적화

리액트의 렌더링

  • 리액트는 렌더링 사이클을 갖고 있음
    • 상태가 변경되면 화면에 반영하기 위해 리렌더링 과정을 거침
  • 렌더링은 메인 스레드의 리소스를 점유하여 성능에 영향을 줌
    • 오래 걸리는 렌더링
    • 불필요한 렌더링
  • React Developer Tools의 Profiler 패널을 이용하여 리렌더링을 확인할 수 있음.
    • Components 패널의 설정 버튼 클릭 후 Highlight updates when components render 항목 체크
C o m p o n e n t s 패 널 의 옵 션
  • 리렌더링 되는 요소의 테두리가 나타남
    • 이 표시를 통해 어떤 컴포넌트가 어느 시점에 렌더링 되었는 지 알 수 있음
    • 렌더링이 필요없는 경우 최적화

컴포넌트 렌더링 시 표시되는 테두리

  • 이미지를 클릭해서 이미지 모달을 띄웠을 때, 모달만 렌더링되지 않고 모달과 전혀 상관없는 헤더와 이미지 리스트 컴포넌트가 렌더링되고 있음
    • 리덕스 스토어를 구독하기 때문
    • useSelector로 최적화

조심해야 할 패턴

  • 셀렉터 안에서 항상 새로운 객체 만드는 경우
  • 셀렉터가 리턴하는 객체 안에서 filter와 같은 신규 객체 만드는 함수 사용하는 경우
  const { category, allPhotos, loading } = useSelector(
    state => ({
      category: state.category.category,
      loading: state.photos.loading,
      photos : state.category.category === 'all' ?  state.photos.data :  state.photos.data.filter(photo => photo.category === category);
    })
  );

useSelector 문제 해결

이 문제를 해결하는 방법은 두 가지가 있음

 

1. 객체를 새로 만들지 않도록 반환값을 나눔

 

verbous 하므로 생략

 

2. Equality Function 사용

 

useSelector의 두번째 인자로 Equality Function을 적용할 수 있음

필터링 결과 데이터는 useSelector 밖에서 필터링 해야 함

 

실습 코드 : 모달 리렌더링 최적화 with useSelector

 

모달 리렌더링 최적화 with useSelector · i0boy/frontend-performance-optimization-4@6cf3992

Show file tree Showing 4 changed files with 93 additions and 32 deletions.

github.com

병목 코드 최적화

이미지 모달 분석 

병목 코드는 Performace 패널을 이용해 찾아냄

 

이미지 갤러리 서비스가 느린 지점 찾기

  1. 페이지가 최초로 로드될 때
    • 그렇게 느리지 않음
  2. 카테고리를 변경했을 때
    • 그렇게 느리지 않음
  3. 이미지 모달을 띄웠을 때
    • 이미지가 늦게 뜸
      • 사이즈 때문에 어쩔 수 없음
    • 배경색이 늦게 변함
  • 모달이 뜨는 과정에서 메인 스레드의 작업을 확인하기
    • 화면이 완전히 로드된 상태로 Performance 패널의 새로고침 버튼이 아닌 기록 버튼을 클릭
    • 이미지를 클릭하여 모달을 띄운 뒤 기록 버튼을 다시 누르면 기록이 종료됨
    • 필요에 따라 네트워크 및 CPU에 throttling 옵션 적용

Performance 패널로 검사한 모달이 뜨는 과정

  1. (1)번에서 이미지 클릭으로 인해 모달이 뜸
    • 이미지 모달이 뜨면 모달 안의 이미지를 로드해야 하기 때문에 Network 섹션에서 이미지가 다운로드 됨
  2. (2)에선 이미지가 모두 다 다운로드됨
    • 작업 명으론 식별할 수 있는게 없지만, 우리는 getAverageColorOflmage 함수가 실행됨을 알 수 있음
    • 마지막에 Image Decode라는 작업이 보임. 이 작업에서 이미지에 관한 처리를 하고 있음
      • 해당 작업은 drawImage 함수(캔버스)의 하위 작업임
  3. Image Decode 작업이 끝나고 (3)과 같이 새롭게 렌더링 되면서 변경된 배경화면이 화면에 보임

getAverageColorOflmage 함수 분석

  • 행당 함수는 이미지의 평균 픽셀 값을 계산하는 함수
  • 캔버스에 이미지를 올리고 픽셀 정보를 불러온 뒤 하나씩 더해서 평균을 냄
    • 큰 이미지를 통째로 캔버스에 올린다는 점
    • 반복문을 통해 가져온 픽셀 정보를 하나하나 더하고 있다는 점

이 코드를 두 가지 방법으로 최적화 시도

 

메모이제이션으로 코드 최적화하기

메모이제이션이란 입력값에 대해 실행한 함수 결과값을 기억하고 있다가,

동일 입력값 조건이 주어질 경우 함수를 실행하지 않고 기억해둔 값을 반환하는 방법(기술)

 

동일 조건 동일 반환값

예시

export const getAverageColorOfImage = imgElement => {
  imgElement.crossOrigin = 'Anonymous';

  const src = imgElement.src;
  if (cache[src]) {
    return cache[src];
  }
  const averageColor = _getAverageColorOfImage(imgElement);
  cache[src] = averageColor;
  return averageColor;
};

이렇게 수정한 후, 하나의 이미지에 대해 모달을 여러 번 띄워 보면,

처음에는 배경이 늦게 뜨지만 이후에는 바로바로 뜸

메모이제이션 적용 후 Performance 패널 검사 결과

동일한 이미지에 대해 첫번째 실행 했을때(1)보다

두 번째 실행했을 때(2) 배경색이  더 빠르게 바뀐 것을 확인할 수 있음

 

메모이제이션의 단점

  • 첫 번째 실행에서는 여전히 느리다
  • 매번 새로운 인자가 들어오는 함수는 메모리만 잡아먹는 골칫거리다
  • 동일 조건에서 충분히 반복 실행되는지 먼저 체크한다.

함수의 로직 개선

이 함수(getAverageColorOflmage)의 느린 코드

  • 캔버스에 이미지를 올리고 픽셀 정보를 불러오는 drawImage와 getImageData함수
  • 모든 픽셀에 대해 실행되는 반복문

이 세가지 요소는 이미지 사이즈에 따라 작업량이 결정됨

즉, 이미지가 사이즈가 작으면 더 빠르게 처리할 수 있으며, 픽셀 수가 줄어들어 반복문의 실행 횟수도 줄어듬

 

이미지를 작은 사이즈로 교체하는 방법을 생각해 볼 수 있음

즉, 섬네일을 활용함.

  • 이미지 사이즈가 작은 섬네일을 활용하여 배경 색을 계산하게 한다면 작업량이 많이 단축될 것임
  • 원본 이미지 다운로드 이전에 계산할 수 있어 더욱 빠르게 배경 색을 적용할 수 있음

메모이제이션과 함수 로직 개선 실습

: https://github.com/i0boy/frontend-performance-optimization-4/commit/701512868473e7a6f03a81a99f841151149ac356

 

섬네일은 PhotoImage에서 갖고 있으니 배경 색을 해당 컴포넌트에서 계산 후 모달에서 가져다 쓰도록 로직 변경

최적화 후 감소한 모달 렌더링 시간

표시된부분이 ge AverageColoroflmage작업.

이전에 약 3.5초 걸리던 작업이 0.1초 가량으로 줄어든 것으로 보임

 

반응형