━━━━ ◇ ━━━━
dev

img에 onError 설정시 걸리는 무한루프

코드를 로컬 서버에서 테스트하던 도중, 엑박 이미지가 계속해서 번쩍거리면서 렉이 걸리는 현상이 발생했다.

개발자도구를 확인해보니 GET https://localhost:3000/assets/images/empty.png 404 (Not Found) 가 반복적으로 호출되고 있었다. 알고보니 잘못된 위치에서 서버를 실행해서 발생된 해프닝이었지만, 만약 서버 오류로 인해 해당 이미지를 가져오지 못하는 상황이 발생한다면 무한루프로 인한 번쩍거림뿐만 아니라 렉으로 인한 사용성 저하까지 불러올 수 있겠다는 생각에 조금 더 알아보기로 했다.

문제는 onError

이미지에는 onError라는 속성이 있는데, src가 null이거나 404 에러가 뜨는 경우에 해당 함수를 실행한다. 따라서 src 이미지가 존재하지 않는 경우에 대체 이미지를 띄우는 용도로 매우 유용하다. 하지만 기존 코드는 src 이미지와 대체 이미지가 모두 존재하지 않는 경우에 onError가 계속해서 유효하지 않은 경로를 src에 할당하게 되므로 무한루프가 일어나게 된다.

따라서 아래와 같은 네개의 코드에 각기 다른 src 이미지와 대체 이미지가 들어왔을 경우를 표로 그려봤다.

// 1
<img
  src={src || NO_IMAGE}
/>

// 2
<img
  src={src}
    onError={({ currentTarget }) => currentTarget.src = NO_IMAGE}
/>

// 3
<img
  src={src || NO_IMAGE}
    onError={({ currentTarget }) => currentTarget.src = NO_IMAGE}
/>

// 4
const [error, setError] = useState(false);
<img
  src={!error ? src : NO_IMAGE}
    onError={() => setError(true)}
/>
(src + 대체) 비교연산자 (1) onError (2) 비교연산자 + onError (3) 비교연산자 + state (4)
valid + valid 정상 정상 정상 정상
null + valid no_image no_image no_image no_image
undefined, “” + valid no_image 엑박 no_image 엑박
404 + valid 엑박 no_image 엑박 no_image
valid + 404 정상 정상 정상 정상
null + 404 엑박 무한루프 무한루프 엑박
undefined, “”, + 404 엑박 엑박 무한루프 엑박
404 + 404 엑박 무한루프 무한루프 엑박

일단 엑박은 유저가 이미지 없음이라고 인지할 수 있겠지만, 무한루프의 경우에는 번쩍거림 뿐만 아니라 무한루프로 인한 렉이 발생하여 사용성 저하가 일어날 수 있으므로 무한루프가 생길 가능성이 있는 코드들은 먼저 제외했다.

(src + 대체) 비교연산자 (1) 비교연산자 + state (4)
valid + valid 정상 정상
null + valid no_image no_image
undefined, “” + valid no_image 엑박
404 + valid 엑박 no_image
valid + 404 정상 정상
null + 404 엑박 엑박
undefined, “”, + 404 엑박 엑박
404 + 404 엑박 엑박

그렇게 제외하고 나니 1, 4번이 남았다. 하지만 onError를 사용하지 않고 404를 우회할 수 있는 방법은 없는 것 같다.

(src + 대체) 비교연산자 + state (4)
valid + valid 정상
null + valid no_image
undefined, “” + valid 엑박
404 + valid no_image
valid + 404 정상
null + 404 엑박
undefined, “”, + 404 엑박
404 + 404 엑박

결국 남은건 state를 사용하는 4번이다. 하지만 src가 유효하지 않은데 no_image가 유효한 경우에는 no_image를 표시할 수 있었으면 좋겠다. 그래서 4를 디벨롭하여 아래와 같은 4.5를 만들었다.

// 4.5
const [error, setError] = useState(false);
<img
  src={!error && src ? src : NO_IMAGE}
    onError={() => setError(true)}
/>

4.5의 경우에는 모든 상황에 최적의 ux를 보여주는듯 하다.

(src + 대체) 비교연산자 + state + src 유효성 확인 (4.5)
valid + valid 정상
null + valid no_image
undefined, “” + valid no_image
404 + valid no_image
valid + 404 정상
null + 404 엑박
undefined, “”, + 404 엑박
404 + 404 엑박

결론

mdn 문서를 보면 이미지를 가져올 수 없을 때 onerror 속성에 오류 처리기를 등록하라고 나와있는데, 막상 onerror 속성에는 deprecated 표시가 되어있다. 그래서 사실 이러한 방법이 맞는지는 모르겠다.

아무튼 지금 상황에서 내릴 수 있는 최선의 결론은 다음과 같다. 혹시라도 더 좋은 방법이 있다면 댓글로 공유해주길 바랍니다.

// 기존 코드
const Image = ({ src }: ImageProps) => {
  return (
    <img
      src={src}
      onError={({ currentTarget }) => {
                currentTarget.src = NO_IMAGE;
            }}
    />
  );
};

// 개선된 코드
const Image = ({ src }: ImageProps) => {
    const [error, setError] = useState(false);
  return (
    <img
      src={!error && src ? src : NO_IMAGE}
      onError={() => setError(true)}
    />
  );
};

참고 사이트

COMMENT
1