ReactJS/Information

[React.js] Lazy Image Component

y0ngha 2021. 12. 15. 22:56

React.js에서 Lazy Image Loading을 구현하기 위한 방법이다.

해당 Component를 사용하게 되면 Lazy Image Loading을 구현할 수 있다.

 

먼저 코드를 보면서 설명해보도록 하겠다.

import React, {useEffect, useRef, useState} from 'react';

interface ILazyImageProps {
    imgSrc: string;
    className?: string;
    alt?: string;
}

let observer: IntersectionObserver | null = null;
const __LOAD_IMAGE__ = "loadImage";

function onIntersection(
    entries: IntersectionObserverEntry[],
    io: IntersectionObserver
) {
    entries.forEach(entry => {
        if (entry.isIntersecting) {
            io.unobserve(entry.target);
            entry.target.dispatchEvent(new CustomEvent(__LOAD_IMAGE__));
        }
    });
}


function LazyImage({imgSrc, className, alt}: ILazyImageProps) {
    const imgRef = useRef<HTMLImageElement>(null);
    const [imgLoad, setImgLoad] = useState(false);

    useEffect(() => {
        const imgEl = imgRef.current;
        imgEl && imgEl.addEventListener(__LOAD_IMAGE__, () => {setImgLoad(true)});
        return () => {
            imgEl && imgEl.removeEventListener(__LOAD_IMAGE__, () => {setImgLoad(true)});
        };
    }, []);

    useEffect(() => {
        if (!observer) {
            observer = new IntersectionObserver(onIntersection, {
                threshold: 0.5
            });
        }
        imgRef.current && observer.observe(imgRef.current);
    }, []);

    return (<img alt={alt} className={className !== undefined ? className : imgSrc} ref={imgRef} src={imgLoad ? imgSrc ? imgSrc : NoImage : NoImage}/>);
}

export default React.memo(LazyImage)

해당 컴포넌트의 Props로 src(이미지 주소), className(밖에서 Style을 쓸 경우에 대한 대비)를 줬다.

 

onIntersection 함수의 역할 ?

  1. IntersectionObserverEntry에 대해 관찰을 해제하고, entry에 대해 "loadImage" 라는 사용자 이벤트를 발생시킨다.

 

IntersectionObserverEntry는 어떻게 들어오는가 ?

  1. new IntersectionObserver()에 callback 함수 안에 들어있다.

 

컴포넌트 생성시

  1. useRef hook을 이용해 img를 등록하고, 이 img에 , "loadImage" 라는 event가 발생 될 경우 state를 변경시켜 Re rendering 시킨다.
  2. 여기서 cleanup(ComponentDidUnmount - useEffect 내 return 함수) 함수로 해당 Component가 Unmount 될 경우 img에 등록되어 있는 이벤트를 삭제한다.(1번작업 삭제)
  3. observer(관찰자)가 null일 경우(초기 로딩된 상태 일 경우) observer를 등록하고, imgRef를 관찰한다.
  4. 만약 0.5(50%) 만큼 img가 보이게 됐다면(로딩이 안된 상태에서의 50%를 뜻함.)  onIntersection 함수가 발동되면서 "loadImage" 라는 evnet를 발생하게 된다.(위 역할 참고)
    (만약 50%가 안됐다면 NoImage를 보여주게 되는데 해당 부분은 따로 Image가 없을 때 보여줄 Image를 import 하면 된다.)
  5. 그렇게 되면 onIntersection 함수에 의해 observer가 해제되게 되고, 이미지 로딩이 끝난다.

그리고 해당 컴포넌트는 React.memo로 src나 className이 바뀌었을 때만 Re rendering하게 설정해주어 렌더링 최적화를 진행한다.

IntersectionObserver는 그 외에도 Infinite Scroll을 구현하거나 할 때 자주 쓰인다.