본문 바로가기
React

Lazy-loading

by kicksky 2021. 5. 8.

이미지가 주된 컨텐츠인 프로젝트(갈피)를 진행하면서도 이미지 최적화와 퍼포먼스는 크게 신경쓰지 못한 것 같아서 이와 관련된 글들을 찾아보게 되었다. 우선, 리액트에서 lazy-loading이 일반적으로 어떻게 구현되는지 알아보았다.

 

첫번째 :: Deepak Vishwakarma, React lazy image loading and TypeScript — No more slow and broken images (201016)

// components/Image/index.tsx

import React from "react";
interface ImageProps extends React.ImgHTMLAttributes<HTMLImageElement> {}
export default ({ ...props }: ImageProps) => {
  return <img {...props} />;
};

 

🏐 우선, 이미지 태그를 리턴하는 컴포넌트 생성

  • 왜 굳이 컴포넌트를 생성하는지 궁금했는데 placeholderImg, errorImg 프로퍼티를 커스텀으로 추가하려는 목적이었다.

🏐 이미지를 불러오기 전에 보여질 placeholder 이미지를 추가

 

🏀 이후에 이미지가 전부 로드되면 동적으로 교체 (비동기 작업)

  • useEffect에서 img 태그를 생성하여 'load' 이벤트 리스너 호출
  • 리스너는 setSrc로 src state를 변경 ( useCallback )*

🏐 이미지 로드 에러 핸들링

  • 동일하게 useEffect에서 'error' 이벤트 리스너를 호출
  • 리스너는 setSrc로 src state를 errorImg로 변경

 

* useCallback을 활용하는 경우가 드물다보니 계속 잊는다. 메모이제이션된 콜백을 반환하는 Hook. 콜백의 메모이제이션된 버전. "불필요한 렌더링을 방지하기 위해 참조의 동일성에 의존적인 최적화된 자식 컴포넌트에 콜백을 전달할 때 유용"

말이 와닿지 않는데 여러 설명을 좀 더 찾아보니 컴포넌트 내부에서 일반 함수를 선언하면 렌더링 될 때마다 새로 함수가 생성되지만 useCallback의 콜백함수는 의존 값이 변할 때에만 새로 생성된다는 것 같다. 렌더링 될 때마다 새로 함수가 생성된다는 건 해당 함수의 메모리 참조값도 새롭게 바뀐다는 뜻이다. 이 경우에는 이 함수를 의존성 배열에 추가하는 것이 의미가 없다. 리액트가 동일한 함수로 인식하지 않기 때문이다. 그러나 useCallback의 경우에는 의존성 배열에 속한 값이 바뀌지 않는 이상 메모리 값이 유지된다. 

 

  const onLoad = useCallback(() => {
    setSrc(src);
  }, [src]);

 

+) TS 적용할 때는 리액트에 정의된 엘리먼트 타입을 extend해서 매개변수의 타입을 지정하는 방식이 흔한 패턴인 것 같다.

 

두 번째 :: Sanish kumar, React Progressive with Lazy-Loading Images [A How-To Guide] (200302)

이미지 로드에 대처하는 몇 가지 방법

  1. placeholder 이미지
  2. baseline image 대신 progressive image
    • 전자는 위에서부터 아래로 이미지가 채워지는 반면, 후자는 블러 필터를 적용한 작은 사이즈의 이미지를 우선 사용하다가 로드가 완료되면 CSS transition으로 원본 이미지를 출력한다.
  3. <img> 태그의 srcset attr.
    • 각각 다른 환경에서 사용될 이미지 소스를 명시하는 속성이다. 브라우저는 이 속성에 따라 device screen size and/or device pixel ratio(DPR)에 맞추어 자동으로 이미지를 선택한다. 

이 글은 라이브러리를 홍보하는 내용이 메인이였던 것 같다.

 

세 번째 :: Antoine Caron, Creating an image lazy loading component with React (190402)

이 글에서 제시하는 코드는 첫번째 글에서처럼 img 태그를 리턴하는 컴포넌트를 만드는 것은 동일한데 placeholder 이미지로 하드코딩된 base64를 사용하여 http request이 없이도 이미지가 보이게 하는 방법을 사용한다. 이 경우에 브라우저에게 이미지를 불러올 시점만 알려주면 된다.

 

이 시점을 알기 위한 도구로 IntersectionObserver API가 있다. 이 API가 제공하는 observe 메소드는 HTML 엘리먼트의 ref를 사용하여 그 엘리먼트가 가시화된 상태인지 확인한다.

useEffect Hook 내부에 IntersectionObserver API를 호출하는 함수를 작성한다. Image 컴포넌트의 ref prop을 imgRef state에 담고 → 이 ref가 뷰포트에 등장하는지를 observer가 캐치하면 → imageSrc를 placeholder에서 원본 이미지의 src로 업데이트 한다. 

 

이 글에서는 특이하게 loading 디스플레이를 어떻게 CSS로 개선시킬지까지 설명한다. 앞선 Image 컴포넌트에 onLoad, onError props를 추가한다. <img> 태그에도 onLoad와 onError 이벤트가 내재했는지 이번에 처음 알았다.* 이 예제에서는 각각 함수를 생성하여 해당 이벤트가 호출될 때 클래스 네임을 추가해주고, CSS로 애니메이션 효과를 주었다.

 

마지막으로 리액트에서 이미지 뿐만 아니라 컴포넌트와 코드까지도 React.lazy, React.Suspense 기능을 통해 lazy load 하는 방식을 소개한다. 16.8.5 버전에서 createFetcher 기능으로 lazy loading과 비동기 렌더 컴포넌트의 실패를 제어할 수 있을 거라고 하는데 실제로 구현되었는지는 한 번 찾아봐야할 것 같다.

 

*onLoad를 네이티브 이벤트로 갖는 엘리먼트는

<body>, <frame>, <frameset>, <iframe>, <img>, <input type="image">, <link>, <script> and <style>가 있다.

 

 


 

 

 

 

'React' 카테고리의 다른 글

forwardRef  (0) 2021.11.22
Kakao Map API에서 마우스 이벤트 핸들링  (0) 2021.04.07
Rendered more hooks than during the previous render  (0) 2021.03.28
Brad Westfall, React: "mount" vs "render"  (0) 2021.02.13

댓글