😫 문제 현상 설명

Geolocation의 Watchposition 훅을 사용해 위치 변화가 감지될 때마다 현 위치를 새로 받아와 보여주는데 새로고침을 하거나 초기 접속 시 파란 빈 화면이 5초 이상 나타났습니다.

 

• 크롬 환경                                                                                               • 사파리 환경

🪐 원인 파악

1. 내가 코드를 잘못 짠 것인가 ?

  • 그럴리가 없었습니다..
  • MDN의 예제 코드를 그대로 사용했기 때문에 제 코드의 문제는 아니라고 판단했습니다.

2. 해당 페이지 성능 문제인가 ?

  • 해당 페이지에는 정말 많은 컴포넌트와 비동기 작업이 많이 일어났기 때문에 단순 성능 문제일 수 있다 판단했습니다.
  • 따라서 저는 모든 코드를 주석처리 후 watchPostion 훅만 실행했습니다. 그러나 동일하게 지연이 발생했습니다.

3. 브라우저 문제 ?

  • 본 프로젝트는 ReactNative를 사용해 웹뷰를 띄우는 방식으로 개발을 하고있었기에 크롬과 사파리를 동시에 띄워 개발하고있었습니다.
  • 그러던 중 사파리에서는 지연이 발생하지 않고, 크롬에선 지연이 발생하는 것을 확인할 수 있었습니다.
  • 따라서 이 지연이 크롬의 문제일 수 있다 판단했고, firefox(비 크로미움), 사파리(비 크로미움), 크롬(크로미움), 웨일(크로미움) 삼성인터넷(크로미움)에서 테스트 해보았습니다.

TMI ) 사실 Firefox에서 테스트가 매우 힘들었습니다.. 이상하게 브라우저 자체에서 렉이 많이 걸리더라구요 ..? 새로고침은 아예 작동하지 않아 새 창을 켜서 localhost로 접속하는 방식을 채택했습니다 (이 또한 창이 많아지면 렉이 걸려 테스트의 한계가 있었습니다)

💡 브라우저 테스트 결론

     
브라우저 환경 테스트 방식 지연시간
Firefox (비크로미움) 새 창을 열어 http://localhost 접속 거의 없음
Safari (비크로미움) 새로고침 15회 1초 이내 ~ 2초
Chrome (크로미움) 새로고침 15회 5초 내외
웨일 (크로미움) 새로고침 15회 5초 내외
삼성 인터넷 (크로미움) 새로고침 15회 5초 내외
  • 사실 크롬에서 지연이 발생하는 것은 큰 문제가 없었습니다.
  • 어차피 웹뷰로 띄워 사용할 예정이니 삼성 인터넷과 Safari에서만 지연이 발생하지 않으면 됐지만 삼성인터넷에서도 지연이 발생했습니다.
  • 이쯤 되니 API가 느린건가..? 라는 생각 + 스택오버플로우에서 봤던 원래 API가 느리다는 글이 맞을 것 같다는 생각이 들었습니다.

 

4. React 렌더링 문제 ?

  • 그냥 쌩 HTML + 바닐라 JS로 해보면 이게 진짜 API 문제인지 확인할 수 있다 생각해 HTML 파일을 만들어 크롬에서 실행시켜보았습니다.
  • 아래 코드에선 watchPostion과 getCurrentPosition이 동시에 사용되고 있지만, 실제 실행시엔 둘 중 하나를 주석처리하고 각각 테스트를 진행했습니다.

 

4.1 테스트 결과

watchPostion의 경우 느리게 동작하는데..
두 함수를 동시에 실행시키니 watchPosition이 실행 완료 될 때까지 getCurrentPosition에 대한 InnerHTML도 실행이 안 되는 것을 볼 수 있었습니다.

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Document</title>
  </head>
  <body>
    <div id="getLat"></div>
    <div id="getLon"></div>
    <div id="watchLat"></div>
    <div id="watchLon"></div>
    <script>
      const getLett = document.getElementById("getLat");
      const getLonn = document.getElementById("getLon");
      const lett = document.getElementById("watchLat");
      const lon = document.getElementById("watchLon");
      lett.style.color = "red";
      lon.style.color = "red";
      getLett.style.color = "blue";
      getLonn.style.color = "blue";
      (function () {
        window.onload = function () {
          var getPos;
          var getSuccess = function (position) {
            getPos = position;
            document.getElementById("getLat").innerHTML =
              getPos.coords.latitude;
            document.getElementById("getLon").innerHTML =
              getPos.coords.longitude;
          };
          navigator.geolocation.getCurrentPosition(getSuccess);
          var watchPos;
          var Success = function (position) {
            watchPos = position;
            document.getElementById("watchLat").innerHTML =
              watchPos.coords.latitude;
            document.getElementById("watchLon").innerHTML =
              watchPos.coords.longitude;
            console.log(watchPos.coords.latitude, watchPos.coords.longitude);
          };
          navigator.geolocation.watchPosition(Success);
        };
      })();
    </script>
  </body>
</html>

 

5. 결론

  • Geolocation API + React + 비 크로미움 브라우저 ⇒ 지연이 거의 없다.
  • Geolocation API + React + 크로미움 브라우저 ⇒ 5초 이상의 지연이 발생.
  • Geolocation API의 getcurrentPosition + 바닐라 JS (브라우저 상관X) ⇒ 지연이 거의 없다.
  • Geolocation API의 watchPostion + 바닐라 JS (브라우저 상관X) ⇒ 5초 이상의 지연이 발생.

결론적으로 API 자체에서 가져오는데에 느리다고 파악하고 다른 방식으로 UX를 개선하기로 결정했습니다.

Memoization이란?

메모이제이션이란 비용이 많이 드는 함수 호출의 결과를 저장하고 동일한 입력이 다시 발생할 때 캐시된 결과를 반환하여 컴퓨터 프로그램의 속도를 높이는 데 주로 사용되는 최적화 기술입니다.

 

예시 코드

function Component ({a, b}) {
    const result = compute(a,b)
    return <div>{result}</div>
}

 

Component 내의 compute 함수가 만일 복잡한 연산을 수행한다면 결과 값을 리턴하는 데에 오랜 시간이 걸리게 될 것입니다.

이럴 때 컴포넌트가 계속 리렌더링 된다면 연산을 수행하는데 오랜 시간이 걸려서 성능에 안 좋은 결과를 미치게 되며 UI 지연현상도 일어나게 될 것입니다.

이러한 현상을 해결해주기 위해서 사용하는 것이 useMemo입니다.

compute 함수에 넘겨주는 a, b의 값이 이전과 동일하다면 컴포넌트가 리 렌더링 되더라도 연산을 다시 하지 않고 이전 렌더링 때 저장해두었떤 값을 재활용 하는 방식입니다.

 

useMemo 적용하기

useMemo로 함수를 감싸준 후에 첫 번째 인수에는 함수를, 의존성 배열에는 compute 함수에서 사용하는 값을 넣어주면 됩니다.

function Component ({a,b}) {
    const result = useMemo(compute(a,b), [a,b]);
    return <div> {result} </div>
}

 

 

 

Vite의 기본 margin

  • vite로 초기 프로젝트를 생성하면 기본적으로 margin이 8px 생긴다
  • 이를 없애려면 index.css 에 코드를 추가해야한다.
#root{
 // 기존에 쓰인 코드
}
body{
    margin: 0,
}
  • 이렇게 해주면 기본적으로 적용된 margin이 없어진다

React, TS, Vite 환경에서 카카오맵 띄우기

  1. 카카오디벨로퍼스에 가서 앱 등록 + 앱 키 받아오기
- 플랫폼 > Web > 사이트 도메인 > [http://localhost:3000](http://localhost:3000) 등록

- JavaScript 키 받아오기

  ![](https://velog.velcdn.com/images/hyorish03/post/a7fbfa50-e75f-4208-80f1-cf9aec81980b/image.png)
  1. .env에 Javascript 키 추가하기
```jsx
VITE_KAKAOMAP_JAVASCRIPT_APP_KEY = 아까 그 앱키
```
  - env가 뭔지 모르면 [ENV 알아보기](https://velog.io/@hyorish03/ReactNative-env%EB%9E%80)
  1. index.html의 head에 카카오맵 script api 가져오기

    ** 주의 ! 최상단의 index.html에서 하세요 ..
    당연한건데 dist > index.html이 먼저 눈에 띄워서 계속 거기서 해서 에러남 .. **

     <script
           type="text/javascript"
           strategy="beforeInteractive"
           src="//dapi.kakao.com/v2/maps/sdk.js?appkey=%VITE_KAKAOMAP_JAVASCRIPT_APP_KEY%&libraries=services,clusterer,drawing"
         ></script>
    • 여기서 strategy="beforeInteractive" 을 이용하면 어떤 것 보다도 이 script가 가장 먼저 로드 되어 kakao api가 로딩이 뒤늦게 되어 발생하는 에러를 막을 수 있다.
  2. TypeScriptd에 필요한 타입정의 패키지 설치하기

    • https://github.com/JaeSeoKim/kakao.maps.d.ts

        # npm
        $ npm install kakao.maps.d.ts --save-dev
      
        # yarn
        $ yarn add kakao.maps.d.ts --dev
    • tsconfig.json의 compilerOptions.types 속성에 패키지 추가

        {
          ...,
          "compilerOptions": {
            ...,
            "types": [
              ...,
              "kakao.maps.d.ts"
            ]
          }
        }
  3. Map을 불러오는 컴포넌트 작성

     /* eslint-disable @typescript-eslint/no-explicit-any */
     import { MutableRefObject, useEffect, useRef } from 'react';
    
     declare global {
       interface Window {
         kakao: any;
       }
     }
    
     function Map() {
       const mapRef = useRef<HTMLElement | null>(null);
    
       const initMap = () => {
         const container = document.getElementById('map');
         const options = {
           center: new window.kakao.maps.LatLng(37.483034, 126.902435),
           level: 2,
         };
    
         const map = new window.kakao.maps.Map(container as HTMLElement, options);
         // eslint-disable-next-line @typescript-eslint/no-explicit-any
         (mapRef as MutableRefObject<any>).current = map;
       };
    
       useEffect(() => {
         window.kakao.maps.load(() => initMap());
       }, [mapRef]);
    
       return <div id="map" style={{ width: '500px', height: '400px' }}></div>;
     }
    
     export default Map;

잘된다.

웹뷰로 지도 띄우는데..

  • 완전 멍충한 짓 함
  • index.html이 dist 내에 하나 그냥 하나 있는데 dist (배포용 폴더)에 있는 index.html에 script 태그 넣어두고 에러난다고 찡찡찡거림
  • 1시간 30분만에 원인을 알아버렸습니다..

새로 배운 것

  • index.html에서 env 변수를 쓰려면 %VITE_APPKEY% 이런 식으로 써야한다.
  • 소스코드에서 쓰는 것처럼 import.meta.env 하면 안 됨

+ Recent posts