웹 성능 최적화 강의 Part.2 요약 2

Kyu·2022년 2월 7일
1
post-thumbnail

프론트엔드 개발자를 위한, 실전 웹 성능 최적화 Part.2 (인프런)
인프런 동영상 강의 내용을 요약 정리하면서 추가적으로 참고한 내용을 덧붙였습니다.

이미지 갤러리 서비스 최적화

  • ✨ Layout Shift 피하기
  • 🔥 이미지 지연 로딩
  • ⚡ useSelect 렌더링 최적화
  • 💥 DrawImage 사용

✨ Layout Shift

Layout Shift가 발생하는 원인

  • 1) 크기가 정해지지 않은 이미지
  • 2) 크기가 정해지지 않은 광고, 임베드 및 iframe
  • 3) 동적으로 주입된 콘텐츠
  • 4) FOIT/FOUT을 유발하는 웹 글꼴
  • 5) DOM을 업데이트하기 전에 네트워크 응답을 대기하는 작업

1) 크기가 정해지지 않은 이미지

  • 이미지에 너비와 높이를 설정하여 레이아웃 이동을 방지하도록 설정한다.
<img src="puppy.jpg" width="640" height="360" alt="강아지와 풍선" />
  • 이미지가 로드되기 전에 width, height 속성을 기반으로 종횡비를 계산하여 레이아웃 계산시에 이 정보를 제공한다.
img { aspect-ratio: attr(width) / attr(height); }

2) 크기가 정해지지 않은 광고, 임베드 및 iframe

  • 광고 슬롯을 위한 고정 공간 확보
  • 과거 데이터를 기반으로 광고 슬롯에 가장 적합한 크기를 선택
  • 브라우저 개발자 도구를 사용하여 최종 임베드 높이를 확인하여 미리 적용

3) 동적으로 주입된 콘텐츠

  • 고정 크기 컨테이너에서 새 컨텐츠로 대체
  • 사용자가 새 콘텐츠를 로드할 수 있도록 더 보기, 새로고침 버튼 등을 사용
  • 사용자와 상호 작용 이전에 컨텐츠 로드

4) FOIT/FOUT을 유발하는 웹 글꼴

  • 핵심 글꼴에 <link rel=preload> 사용하고 font-dispaly: optional 과 조합하여 사용

🔥 이미지 지연 로딩 (react-lazyload)

  • 이미지 지연 로딩을 사용하여 미리 영역을 할당하고 뷰포트에 표시되는 시점에 이미지를 로딩한다.
import React from 'react';
import ReactDOM from 'react-dom';
import LazyLoad from 'react-lazyload';
import MyComponent from './MyComponent';

const App = () => {
  return (
    <div className="list">
      <LazyLoad height={200}>
        <img src="tiger.jpg" />                          
      </LazyLoad>
      <LazyLoad height={200} once>                        
        <MyComponent />
      </LazyLoad>
      <LazyLoad height={200} offset={100}>
        <MyComponent />
      </LazyLoad>
      <LazyLoad>
        <MyComponent />
      </LazyLoad>
    </div>
  );
};

ReactDOM.render(<App />, document.body);

⚡ useSelector 렌더링 최적화

  • useSelector는 redux의 상태값 조회를 위한 hook이다.
  • useSelector를 사용하면 객체를 새로 만들기 때문에 상태변경에 상관없이 불필요한 리렌더링이 발생한다.
const { number, diff } = useSelector(state => ({
  number: state.counter.number,
  diff: state.counter.diff
}));

useSelector를 분리하여 값으로 취급하는 방법

const number = useSelector(state => state.counter.number);
const diff = useSelector(state => state.counter.diff);

shallowEqual을 사용하여 비교후에 렌더링 하는 방법

 const { number, diff } = useSelector(
    state => ({
      number: state.counter.number,
      diff: state.counter.diff
    }), 
    shallowEqual
  );

useSelector의 객체 렌더링 문제 해결 (reselect, redux-toolkit)

  • useSelector는 내부에서 새로운 객체가 할당되는 경우 변경 유무와 관계 없이 리렌더링이 발생한다.
// 문제가 발생하는 코드, filter에 의해 매번 새로운 배열이 할당되므로 리렌더링이 발생
const users = useSelector(
  (state) => state.users.filter(user => user.subscribed)
); 
  • 이 문제는 메모이제이션을 사용하는 reselect 또는 redux-toolkitcreateSelector를 사용하여 해결할 수 있다.
// userSelector.jsx
import { createSelector } from 'reselect';
const userSelector = createSelector(
  state => state.users,
  users => users.filter(user => user.subscribed)
)
export default userSelector;

// App.jsx
import { useSelector } from 'react-redux';
import userSelector from '../selector/userSelector'
function App = () => {
  const user = useSelector(selectFilteredPhotos);
  // ...
}

💥 Canvas에 DrawImage 사용시 상황에 맞게 크기 줄이기

  • 이미지의 RGB 값이 필요한 경우 원본 사이즈 이미지를 축소 후 이미지 데이터를 추출하는 형태로 구현
const width = imgElement.naturalWidth || imgElement.offsetWidth || imgElement.width;
const height =imgElement.naturalHeight || imgElement.offsetHeight || imgElement.height;

canvas.width = width / 3;
canvas.height = height / 3;
context.drawImage(imgElement, 0, 0, canvas.width, canvas.height);

const imageData = context.getImageData(0, 0, canvas.width, canvas.height).data;
const length = imageData.length;

캔버스 최적화

0개의 댓글