사용하는 sdk 서비스가 응답에 관련된 인터페이스를 제공하지 않을 때

Dada·2023년 9월 23일
0

Dev log

목록 보기
2/9

이번 프로젝트 사이트를 만들면서 아주 골치아픈 부분이 있었다.
탭과 스크롤 문제였는데 그 중에서도 모바일에서 [더보기] 버튼과 그 안에 숨겨져있던 sdk 로딩 타이밍에 대한 이슈로 해결을 위한 이런저런 아이디어를 많이 내보았었다.

네이버 쇼핑에서 사용하는 탭 버튼과 그 이동이 비슷한 동작이라 예시로 네이버 탭이 지원하는 네가지 항목을 가져왔다. ( 네이버 탭을 예시로 들어 글을 쓰려한다.)

상품을 하나 들어가면 대부분의 상세 페이지가 말도 안되게 긴 상품들이 있기 때문에 모바일에선 스크롤 해야하는 길이가 너무 길어 보통은 더보기 버튼을 두고 숨겨둔다.
여기서 설명하고 싶은 시나리오는 리뷰Q&A 부분에 해당하는 영역이 모바일에서 더보기 버튼을 누르지 않은 상황에서는 숨겨져 있는 상태인 것이다.

조건

  • 모바일 상태 / 모바일 사이즈의 웹 상태
  • 더보기 버튼이 노출되어 있을 때 (리뷰와 Q&A 탭에 해당하는 영역 노출이 되지 않은 상태)

동작 정의

  • 탭의 리뷰 버튼 클릭
    -> 더보기 버튼이 사라지고 리뷰와 Q&A 탭에 해당하는 영역 노출
    -> 리뷰 영역의 위치를 찾아 스크롤 이동

발생하는 문제

  • 리뷰 버튼을 클릭하여 리뷰영역을 찾아 이동하는 속도에 비해 리뷰 영역에 들어와야하는 솔루션의 로딩 시간이 오래 걸림
  • 리뷰의 영역이 불러오지 못하는 동안 영역 확보가 안되어 Q&A로 탭이 이동되는 현상 발생

해결책을 생각해보자

처음 내가 이 문제를 직면하고 든 생각은 두가지였다.
1. SSR로 데이터를 받아와 사용하는 sdk의 지연 막기
2. 응답 상태를 받아올 수 있는 콜백을 인터페이스로 두어 응답이 들어올 때 캐치하기

SSR

1번은 이미 선임님이 클라이언트단에서 작업을 마치신 상태여서 시도하기 어려운 방식이었다.

여기서의 의문. 왜 client에서 가져오는 동작방식을 선택하셨을까?

고민한지 오래지나지 않아 답을 찾을 수 있었는데, 페이지에 진입시 불러오지 않고 사용자가 특정 동작(더보기 버튼 클릭과 같은)을 할 때 동적으로 로드된다는 것에 있다. 사용자가 실제로 필요로 하는 시점에만 sdk를 로드함으로써 불필요한 자원사용을 줄이고 페이지 성능을 최적화 할 수 있다는 것 또한 csr을 선택하신 이유로도 충분히 납득이 갔다.
이렇게 고민해보고 이유를 찾다보니 ssr과 csr에 대한 적시 적소의 사용에 대해서도 다시금 생각해보게 되었다.

인터페이스

콜백을 넘겨주어 상태를 캐치해 처리하는 것이 가장 해결하기 쉬운 방법이라 생각했는데 실상 그렇지 않았다.
사용하는 sdk가 약간은 옛 서비스다보니 응답에 대한 인터페이스를 따로 제공해주지 않고 있었다는 것이었다. 그렇다면 어떻게 그 타이밍을 알아내어 동작에 무리 없이 잘 넘어갈 수 있을까?
또 다른 방법을 생각해내야 했다.

해결되지 않은 문제, 또 다른 해결책

생각보다 별 거 아닌 것 같은데 생각보다 쉽게 풀리지 않았다. 그리하여 또 다시 머리를 굴려 생각해낸 해결책은 아래와 같다.

  1. 임시영역을 잡았다가 치고 빠지는 setTimeout
  2. offsetHeightresizeOberver

임시영역과 setTimeout

그렇다면 리뷰가 들어오기 전에 임의의 영역을 잡아두고 대략 리뷰가 들어올때 쯤 빼버리는 것은 어떨까?
약간은 야매스러웠지만 setTimeout을 사용하여 일정시간을 두고 임시 영역을 빼보았다.
(예제 코드)

cosnt [isTempAreaVisible, setIsTempAreaVisible] = useState<boolean>(true);

useEffect(() => {
  const timerId = setTimeout(() => {
    setIsTempAreaVisible(false);
  }, 300);
  return () => clearTimeout(timerId);
}, [)

return(
  <>
    <Wrapper id="review-area">
       <sdk/>
      {isTempAreaVisible && <TempArea style={{height:"800px"}}/> }
    </Wrapper>
  </>
)

이렇게 되면 사용하는 sdk를 감싼 컴포넌트가 나오기 전에 임시 TempArea가 0.3초 영역을 차지하여 리뷰가 0.1초만에 들어오면 밑으로 밀려났다가 사라지기 때문에 꽤나 좋은 방법이라는 생각을 했었다.
하 . 지 . 만
아주 어리석은 판단이었지..
인터넷이 어지간히 빠른 곳에서 혹은 모바일 5g가 잘 터지는 사용자는 문제 없이 0.3초만에도 불러오지만 데이터의 양이 많을수록, 인터넷이 잘 안 될 수록 0.3은 택도 없었다. setTimeout의 시간을 500, 1000, 3000, 5000 까지 늘려갈 때 즈음 굉장한 현타가 찾아오며 이건 아니지 않냐고 스스로에게 반문하였던 순간이었다. 분명 3000에서 깨달았지만(모두가 3000을 충족하는 네트워크와 사양의 컨디션이 아닐 것이란 걸) 흐린눈으로 5000까지 갔던 나의 게으름을 반성한다.

개발자도구 네트워크에 들어가 slow 3g로 맞춰놓고 보면 아주... 재밌다.

offsetHeight + resizeObeserver

그러다 문득 review-area의 높이 변화로 리뷰 영역이 들어오는 것을 알아챌 수 있다면 어떨까 라는 생각이 번뜩 머릿속에 떠올랐다. setTimeout으로 테스트해볼 때 잡아둔 임시 영역의 높이값을 알고 있고 review-area의 높이가 임시 영역의 높이보다 높아지는 순간 (= 리뷰 영역이 들어오는 순간) 임시영역을 빼버린다면??
그렇다면 우선 element의 id를 추적하여 실시간으로 돔의 높이 변화를 추적하고 관리해야겠다는 생각이 들었다. 찾아보니 web api 중 resizeObserver라는 게 있었다.
(예제 코드)

useEffect(() => {
  //ref가 접근하는 돔의 현재 위치를 reviewArea에 할당
  const reviewArea = reviewAreaRef.current;

  if (reviewArea) {
    const observer = new ResizeObserver((entries) => {
      for (const entry of entries) {
        .
		.
        // 임시 영역인 800보다 review-area의 높이가 커진다면 임시 영역은 false로 처리해준다.
          setIsTempAreaVisible(false); 
        }
      }
    });

    observer.observe(reviewArea);

    return () => {
      observer.disconnect();
    };
  }
}, [reviewAreaHeight]); 


return(
  <>
    <Wrapper id="review-area">
       <sdk/>
      { isTempAreaVisible && <TempArea style={{height:"800px"}}/> }
    </Wrapper>
  </>
)

영역에 스피너까지 추가하니 리뷰가 들어오는 타이밍 전엔 스피너로 리뷰 영역이 그려지는 것을 사용자에게 시각적으로 보여주고 자연스럽게 리뷰 영역이 들어올 때 사라지는 성공적인 효과를 확인할 수 있었다!
보다 나은 해결 방안이 있을 것 같기도 한데 아마 더 고민하다보면 또 더 좋은 방법이 나올지도 모르겠다!

profile
우당탕탕 개발로그

0개의 댓글