[React] geolocation API hook

Haz·2024년 2월 20일
0

개발여행기

목록 보기
21/24

geolocation API


geolocation은 GPS 또는 네트워크를 이용하여 현재 위치 정보를 객체로 반환하는 API로, 웹브라우저에서 지원한다.

웹브라우저에서 현재 내 위치의 위도(latitude)와 경도(longitude)를 확인해 프로젝트에 사용하고 싶을 때 활용하는데, 필자는 날씨 앱을 만들기 위해서 해당 API가 필요했다.

geolocation Methods

geolocation은 navigator.geolocation를 호출해 데이터를 요청해 객체를 반환하기 때문에, 이 객체를 이용할 수 있는 메서드 몇 가지로 원하는 기능을 만들면 된다.

  • getCurrentPosition: 현재 위치를 가져온다.
  • watchPosition: 지정된 시간마다 현재 위치를 가져온다.
  • clearWatch: WatchPosition을 멈춘다.

프로젝트에서는 현재 위치를 가져오는 걸 베이스로 위치 변경을 감지하는 기능도 구현하고자 watchPosition까지 활용하기로 했다.

Hook으로 만들기


바닐라 JavaScript를 활용할 때는 geolocation API를 활용해 호출하고 메서드를 쓰면 됐지만, React-TS 기반의 프로젝트에서는 이를 어떻게 활용하면 될 지 모르겠어서 모두의 친구, 구글에게 물어봤더니 Hook으로 만들어서 구현한 분들이 많았다.

getCurrentPosition으로 현재 위치 가져오기

그 중, getCurrentPosition을 활용한 Hook은 어떤 개발자 분이 이미 훌륭하게 TS 버전으로 구현해두신 게 있어서 코드를 뜯어보면서 공부했다.

import { useState, useEffect } from 'react'

interface ILocation {
  latitude: number
  longitude: number
}

export const useGeoLocation = (options = {}) => {
  const [location, setLocation] = useState<ILocation>()
  const [error, setError] = useState('')

  const handleSuccess = (pos: GeolocationPosition) => {
    const { latitude, longitude } = pos.coords

    setLocation({
      latitude,
      longitude,
    })
  }

  const handleError = (err: GeolocationPositionError) => {
    setError(err.message)
  }

  useEffect(() => {
    const { geolocation } = navigator

    if (!geolocation) {
      setError('Geolocation is not supported.')
      return
    }

    geolocation.getCurrentPosition(handleSuccess, handleError, options)
  }, [options])

  return { location, error }
}

출처: [React] geoLocation API로 현재 위치를 알아보자

watchPosition으로 위치 변경 감지하기

해당 블로그에서 훅을 만들기 위해 참고한 레퍼런스를 확인하니 블로그가 하나 있길래 그것도 확인해봤다. 그랬더니 TypeScript 버전은 아니지만, JS 버전으로 훅을 만든 깃헙 레포 주소가 있었다.

그걸 기반으로 getCurrentPosition 훅을 참고해 TypeScript 버전으로 수정해서 만들 수 있었다.

export const useWatchLocation = (options = {}) => {
  // 내 위치 정보 저장
  const [loc, setLoc] = useState<ILocation>();
  // 에러 메시지 저장
  const [error, setError] = useState("");
  // watch 인스턴스 취소가 가능하도록 'watchPosition'에서 반환된 ID를 저장
  const locationWatchId = useRef(0);

  // 성공 콜백
  const handleSuccess = (pos: GeolocationPosition) => {
    const { latitude, longitude } = pos.coords;

    setLoc({
      latitude,
      longitude,
    });
  };

  // 에러 콜백
  const handleError = (err: GeolocationPositionError) => {
    setError(err.message);
  };

  // 저장된 watchPosition ID를 기반으로 감시 인스턴스 삭제
  const cancelLocationWatch = () => {
    const { geolocation } = navigator;

    if (locationWatchId.current && geolocation)
      geolocation.clearWatch(locationWatchId.current);
  };

  useEffect(() => {
    const { geolocation } = navigator;

    // 웹브라우저에서 위치 수집에 실패하는 경우 오류로 처리
    if (!geolocation) {
      setError("위치 정보 수집에 실패했습니다.");
      return;
    }

    // Geolocation API로 위치 감시 시작
    locationWatchId.current = geolocation.watchPosition(
      handleSuccess,
      handleError,
      options
    );

    // React가 사용된 구성 요소를 마운트 해제할 때 위치 감시 인스턴스 삭제
    return cancelLocationWatch;
  }, [options]);

  return { loc, cancelLocationWatch, error };
};

문제 해결하기

JS버전을 TS 버전으로 수정하면서 발견한 수정 포인트가 몇 군데 있었다.

  1. useState, useRef 훅에도 타입 설정 혹은 기본값 설정이 필요함
  2. success, fail 시 받는 반환 객체를 일종의 튜플 타입처럼 활용해 매개변수로 받기

1번의 경우에는 생명주기 훅에 타입을 별도로 설정하지 않거나, 기본값을 비워두거나 null이 들어가니 에러가 발생했다. 그래서 위치 정보를 저장하는 상태에는 제네릭을 활용해 타입 선언하고, 에러를 저장하는 상태에는 에러 메시지를 사용할 거라 기본값을 string으로, Ref는 id를 반환하기 때문에 기본값을 number 타입으로 지정해 문제를 해결했다.

2번은 문제라기보다 수정하면서 알아낸 와우 포인트랄까? 성공 여부에 따라 반환되는 객체를 매개변수로 받아 타입으로 활용할 수 있다는 깨달음을 얻었다.

정리하기

geolocation API를 리액트에서 활용하기 위해 Hook으로 만들어 사용할 수 있도록 ts 파일을 만들어봤다. 이제 이걸 잘 활용해서 기능을 구현하는 일만 남았다. 오늘도 선배 개발자들의 경험을 밑거름 삼아 한 발 더 나아갔다.

profile
나도 재밌고, 남들도 재밌는 서비스 만들어보고 싶다😎

0개의 댓글