[react-globe.gl] 지구본 시점 위치 기억해두기 by 세션스토리지

현재 마무리 진행 중인 Vite+TypeScript 프로젝트에서 react-globe.gl 라이브러리를 활용해 국가별 여행 위험 경보를 지구본에 표시하고 있습니다.

 

완성본

이번 글에서는 국가 디테일 페이지(예: 이탈리아)로 이동한 후 다시 지구본 페이지로 돌아와도 기존의 지구본 뷰(시점)를 유지하는 방법을 소개하고자 합니다.

문제: 매번 초기화되는 지구본 뷰

기존 코드에서는 사용자가 지구본 페이지에 접근할 때마다 지구본의 시점이 항상 대한민국으로 초기화되었습니다. 이로 인해 사용자는 매번 동일한 초기 위치에서 시작해야 했으며, 페이지를 떠난 후 다시 돌아왔을 때 이전에 보던 위치가 기억되지 않았습니다.

또한, 현재 진행중인 데브코스의 FT님께서 제 페이지를 사용해보신 후, "지구본 페이지로 돌아올 때 이전에 보던 지구본 상태를 유지해 보여주는 것도 좋을 것 같다"는 피드백을 주셨습니다. 이 피드백에 따라, 사용자 경험을 더욱 개선하기 위해 세션 스토리지를 사용한 기능을 추가로 반영하게 되었습니다. 

해결: 세션 스토리지를 이용한 지구본 뷰 상태 관리

이번 포스팅에서는 세션 스토리지를 사용하여 지구본의 위치(뷰 상태)를 저장하고 불러오는 방법을 소개하고자 합니다.

이를 통해 사용자가 페이지를 떠났다가 다시 돌아왔을 때, 지구본이 마지막으로 보던 위치를 그대로 유지할 수 있도록 하였습니다. 이로써 보다 직관적인 사용자 경험을 제공할 수 있게 되었습니다.

이번 포스팅에서는 그 구현 과정을 자세히 다뤄보겠습니다.


🔍 복습 ) 처음 접속 시점을 대한민국으로 설정하는법

처음 접속할 때는 기본적으로 대한민국이 보이도록 설정하여 처음 방문하는 사용자에게는 친숙한 위치를 제공하고 있습니다.

이를 설정하는것은, 이전 포스팅을 통해 다뤘으니 참고하실분은 아래의 글을 확인해주세요. 🙃

2024.10.01 - [FE 개발일지] - [react-globe.gl][성능 개선] 초기 지구본 위치를 대한민국으로 설정하고, 초기 위치 풀리지 않도록 만들기

 

[react-globe.gl][성능 개선] 초기 지구본 위치를 대한민국으로 설정하고, 초기 위치 풀리지 않도록

현재 개인프로젝트로, vite+typescript 프로젝트 환경에서 react-globe.gl 라이브러리를 이용하여지구본으로 국가별 여행위험경보를 띄우고 있다. 문제상황https://github.com/vasturiano/react-globe.gl GitHub - vast

developer-dalsu.tistory.com

 

1. 세션 스토리지를 이용한 상태 저장과 복원 ( in useGlobeVies.ts hook)

기존에는 페이지를 새로고침하거나 다른 페이지로 이동 후 돌아오면, 지구본의 위치가 기본값(대한민국)으로 초기화되었습니다. 하지만 세션 스토리지를 사용함으로써 사용자가 보고 있던 위치를 기억하고, 페이지가 다시 로드될 때 그 위치를 복원할 수 있게 개선해보았습니다.

 

 1-1. saveCurrentView 함수 추가

지구본의 현재 위치를 세션 스토리지에 저장해줍니다.

사용자가 지구본을 이동한 후 다른 페이지로 가거나 탭을 전환할 때 뷰 상태를 저장하는 역할입니다.

 

로컬스토리지를 사용하면 다시 웹페이지에 접속시 언제봤을지 모르는 뷰 상태가 남아있을 수 있기 때문에, 

웹페이지를 끄고 다시 접속시에 시점이 초기화 될 수 있도록 세션스토리지를 사용해주었습니다.

export const useGlobeView = ({
  globeRef,
  countriesData,
}: UseGlobeViewProps) => {
	...
    const saveCurrentView = useCallback(() => {
      if (globeRef.current) {
        const currentView = globeRef.current.pointOfView();
        sessionStorage.setItem("globeView", JSON.stringify(currentView));
      }
    }, [globeRef]);
    ...
}
참고할 점은, useCallback() 을 사용해야 불필요한 렌더링을 방지할 수 있습니다.

'초기 뷰 설정'과 관련된 앞으로의 거의 대부분의 함수에 불필요하게 렌더링되는것을 막고자, useCallback()을 사용하였습니다.

 1-2. loadSavedView 함수 추가

저장된 지구본의 뷰 상태를 불러와줍니다.

만약 저장된 뷰가 없다면 기본값으로 대한민국을 반환하도록 하였습니다.

interface UseGlobeViewProps {
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
  globeRef: React.MutableRefObject<any>;
  countriesData: {
    features: GeoJsonFeature[];
  } | null;
}

const DEFAULT_VIEW: GlobeView = {
  // default : 대한민국
  lat: 35.9078,
  lng: 127.7669,
  altitude: 3,
};

export const useGlobeView = ({
  globeRef,
  countriesData,
}: UseGlobeViewProps) => {
	...
    const loadSavedView = useCallback((): GlobeView => {
      const savedView = sessionStorage.getItem("globeView");
      return savedView ? JSON.parse(savedView) : DEFAULT_VIEW;
    }, []);
    ...
}
 

 1-3. initialViewSet ref 추가

지구본의 초기 뷰 설정을 한 번만 수행하기 위해 initialViewSet이라는 useRef를 사용하여 불필요한 재렌더링을 방지해줍니다.

export const useGlobeView = ({
  globeRef,
  countriesData,
}: UseGlobeViewProps) => {
    const initialViewSet = useRef(false);	// 불필요한 재렌더링 방지
    ...
    const saveCurrentView = ...
    const loadSavedView = ...
    const setInitialView = ...
}
 

2. 페이지 가시성 변경 및 언로드 이벤트 리스너 추가

saveCurrentView()를 활용하고 적용하기 위해,  visibilitychange와 beforeunload 이벤트를 추가했습니다.

사용된  visibilitychange와 beforeunload 이벤트를 간단히 설명하자면
  • visibilitychange: 사용자가 다른 탭으로 전환하거나 페이지가 숨겨질 때(hidden 상태) 뷰를 저장합니다.
  • beforeunload: 사용자가 페이지를 떠나거나 새로고침할 때 현재 뷰를 저장합니다.
export const useGlobeView = ({
  globeRef,
  countriesData,
}: UseGlobeViewProps) => {
	... 
	const initialViewSet = useRef(false);
    const saveCurrentView = ...
    const loadSavedView = ...
    const setInitialView = ...
	... // -------------------  앞의 변경사항들  ---------------------

    useEffect(() => {	// 추가
      const handleVisibilityChange = () => {
        if (document.visibilityState === "hidden") {
          saveCurrentView();
        }
      };

      window.addEventListener("visibilitychange", handleVisibilityChange);
      window.addEventListener("beforeunload", saveCurrentView);

      return () => {
        window.removeEventListener("visibilitychange", handleVisibilityChange);
        window.removeEventListener("beforeunload", saveCurrentView);
        saveCurrentView();
      };
    }, [saveCurrentView]);
    ...
}

 

이 코드를 통해 페이지를 떠나기 직전이나 혹은 숨겨질 때 지구본의 뷰가 저장되도록 해서,

사용자가 다시 돌아왔을 때 지구본이 동일한 상태를 유지하도록 하였습니다. 

 

3. BannerPage.tsx 변경 사항

이제 앞에서 작성한 useGlobeView에서 반환된 saveCurrentView 함수를 활용하여, 지구본의 국가를 클릭하거나 페이지를 떠날 때 지구본의 뷰를 저장하는 기능을 추가했습니다.

 

3-1. 국가 클릭 시 뷰 저장

사용자가 지구본에서 국가를 클릭하면, 클릭 직전의 뷰가 저장되어 나중에 돌아와도 해당 국가로 이동하기 전 상태가 복원됩니다.

const BannerPage: React.FC = () => {	
	...
	const navigate = useNavigate();
    const { key, saveCurrentView } = useGlobeView({	// 추가
    globeRef,
    countriesData: countriesData ?? null,
  });
    
    ...
    
    const handlePolygonClick = useCallback(
      (polygon: object) => {
        const geoPolygon = polygon as GeoJsonFeature;
        saveCurrentView();  // 추가, 국가 클릭 시 현재 뷰 저장
        navigate(`/${geoPolygon.properties.country_nm}`);
      },
      [navigate, saveCurrentView]
    );
    
    ...
}

3-2. 컴포넌트 언마운트 시 뷰 저장

사용자가 페이지를 떠날 때에도 현재 뷰가 저장되도록 useEffect를 사용해 언마운트 시점에 뷰를 저장하는 로직을 추가했습니다.
const BannerPage: React.FC = () => {	
	...
    
	useEffect(() => {	// 추가
      return () => {
        saveCurrentView();  // 컴포넌트 언마운트 시 뷰 저장
      };
    }, [saveCurrentView]);
    
    ...
}

완성본

국가 디테일 페이지(이탈리아 등)로 이동한다음 다시 지구본 페이지로 돌아오더라도, 기존 지구본 뷰(시점)가 유지되는것을 확인할 수 있습니다.

 

 

마치며

 이번 작업을 진행하면서 몇 가지 시행착오를 겪었습니다.

 처음에는 이전에 썼던 발행글처럼, 페이지가 새로고침되거나 다른 탭으로 이동할때 'default로 설정된 아프리카 대륙 보다는 대한민국이 낫겠지~ ' 하고 가볍게 생각했었습니다. 이때, 사용자가 보고 있던 지구본 상태가 사라져 불편함을 느낄 수 있다는 문제를 쉽게 지나쳤던것 같습니다. 대한민국으로 초기 설정하는 것만으로 충분할 것이라고 생각했지만, 실제로는 사용자가 보고 있던 위치를 기억하는 것이 사용자 경험 개선에 있어 더 중요한 부분이었던 것 같습니다.

 이 문제를 해결하기 위해 세션 스토리지를 사용해 뷰 상태를 저장하고 불러오는 기능을 구현했습니다. 이 과정에서 사실 처음엔 로컬스토리지로 구현했었는데, 만드는 도중에 로컬 스토리지와 세션 스토리지의 차이점이 떠올라 세션 스토리지로 다시 선택하여 개선한 것이 옳은 결정이었다고 생각합니다. 만약 로컬 스토리지를 사용했다면, 사용자가 언제 마지막으로 웹페이지를 접속했는지에 따라 불필요한 뷰 상태가 계속 남아 있을 수 있었기 때문입니다. 

 또한, useRef와 useCallback을 활용해 불필요한 리렌더링을 방지하는 방법을 적용하면서, 성능 최적화 측면에서도 중요한 경험을 쌓을 수 있었던 것 같습니다.

 

피드백을 받아 웹페이지를 개선시켜본 좋은 경험으로 발전시켜 뿌듯해지는 하루였습니다.🍀👍🏻