Pull to refresh

Создание React-ивного хука usePosition() для получения и отслеживания координат браузера

Reading time4 min
Views11K

image


Если вкратце


В этой статье мы создадим React-ивный хук usePosition() для отслеживания геолокации браузера. Под капотом этот хук будет использовать методы getCurrentPosition() и watchPosition() нативного браузерного объекта navigator.geolocation. Финальную версию хука я опубликовал на GitHub и NPM.


Зачем создавать хук usePosition() в принципе


Одно из важных преимуществ хуков в React-е — это возможность изолировать логически связанные фрагменты кода в одном месте (в хуке), избежав при этом необходимости смешивания логически не связанных фрагментов кода, например, в методе компонента componentDidMount().


Предположим, мы хотим получить координаты браузера (latitude и longitude) и после получения координат запросить прогноз погоды или текущую температуру в этом регионе со стороннего сервиса. Код этих двух функциональностей (получения координат и запроса температуры) в React-е часто размещают внутри одного метода componentDidMount(). При этом в методе componentWillUnmount() обычно "убирают" за собой, вызывая метод clearWatch() для прекращения слежки за локацией браузера. Подобный подход увеличивает размер методов, разбивает логически связанные участки кода на части (отдельно подписка и отписка от слежки за локацией браузера) и объединяет логически слабо связанные части кода в один метод (получение координат и температуры). Чтение кода затрудняется, так же как и его отладка и поддержка.


Далее мы попробуем вынести функциональность, связанную с получением координат браузера, в отдельный хук usePosition(), чтобы избежать перечисленные выше трудности.


Как мы будем использовать хук usePosition()


Пойдем "от противного" и перед имплементацией самого хука давайте спланируем, как мы его будем использовать. Это поможет нам определиться с интерфейсом хука (что он будет принимать и что возвращать).


Простейший пример получения координат и их отображения на экране может выглядеть так:


import React from 'react';
// Импортируем наш хук здесь.
import { usePosition } from './usePosition';

export const UsePositionDemo = () => {
  // Получаем позицию браузера (или ошибку) здесь.
  const { latitude, longitude, error } = usePosition();

  // Выводим координаты на экран одной строкой (красота пока не нужна).
  return (
    <>
      latitude: {latitude},
      longitude: {longitude},
      error: {error}
    </>
  );
};

Всего одна строчка с хуком usePosition() и мы можем оперировать координатами latitude и longitude. Мы даже не использовали здесь явно хуки useState() или useEffect(). Подписка на отслеживание координат, а так же удаление подписчика инкапсулированы в один хук usePosition(). Дальше магия перерисовки компонентов React-а сделает все за нас. В начале координаты будут равны null или undefined. Как только координаты будут получены, компонент будет перерисован и мы увидем их на экране.


Реализация хука usePosition()


Наш хук usePosition() является обычной JavaScript функцией, которая выглядит так:


// Импортируем зависимости.
export const usePosition = () => {
  // Здесь будет код хука.
}

Мы будем использовать хуки useState() для внутреннего хранения координат и useEffect() для подписки и отписки от слежения за координатами. Для этого мы должны их импортировать


import { useState, useEffect } from 'react';

export const usePosition = () => {
  // Здесь будет код нашего хука.
}

Создадим переменные состояния, в которых будем хранить координаты или ошибку получения координат (например, если пользователь откажется делиться своим местонахождением).


import { useState, useEffect } from 'react';

export const usePosition = () => {
  const [position, setPosition] = useState({});
  const [error, setError] = useState(null);

  // Код хука...
}

Так же на этом этапе мы можем вернуть переменные, которые ожидаются от хука. Пока что в этих переменных нет ничего полезного, но мы скоро это исправим.


import { useState, useEffect } from 'react';

export const usePosition = () => {
  const [position, setPosition] = useState({});
  const [error, setError] = useState(null);

  // other code goes here...

  return { ...position, error };
}

А теперь ключевой момент имлементации — получение координат.


import { useState, useEffect } from 'react';

export const usePosition = () => {
  const [position, setPosition] = useState({});
  const [error, setError] = useState(null);

  // Здесь у нас будут колбеки, упомянутые в коде ниже..

  useEffect(() => {
    const geo = navigator.geolocation;

    if (!geo) {
      setError('Геолокация не поддерживается браузером');
      return;
    }

    // Подписываемся на изменение геопозиции браузера.
    watcher = geo.watchPosition(onChange, onError);

    // В случае, если компонент будет удаляться с экрана
    // производим отписку от слежки, чтобы не засорять память.
    return () => geo.clearWatch(watcher);
  }, []);

  return {...position, error};
}

В хуке useEffect() мы сначала делаем проверку на то, поддерживает ли браузер функциональность определения координат. Если функциональность не поддерживается, мы выходим из хука с ошибкой. Иначе мы подписываемся на изменение геопозиции браузера используя колбеки onChange() и onError() (мы добавим их код ниже). Обратите внимание, что из хука useEffect() мы возвращаем анонимную функцию, которая будет выполнена в случае, если компонент будет удаляться из отображения. В этой анонимной функции мы производим отписку от слежки, чтобы не засорять память. Таким образом вся логика подписки и отписки от слежения находится в одном хуке usePosition().


Давайте добавим отсутствующие колбеки:


import { useState, useEffect } from 'react';

export const usePosition = () => {
  const [position, setPosition] = useState({});
  const [error, setError] = useState(null);

  const onChange = ({latitude, longitude}) => {
    // Здесь мы могли бы сохранить весь объект position, но для
    // ясности давайте явно перечислим, какие свойства нас интересуют.
    setPosition({latitude, longitude});
  };

  const onError = (error) => {
    setError(error.message);
  };

  useEffect(() => {
    const geo = navigator.geolocation;

    if (!geo) {
      setError('Геолокация не поддерживается браузером');
      return;
    }

    // Подписываемся на изменение геопозиции браузера.
    watcher = geo.watchPosition(onChange, onError);

    // В случае, если компонент будет удаляться с экрана
    // производим отписку от слежки, чтобы не засорять память.
    return () => geo.clearWatch(watcher);
  }, []);

  return {...position, error};
}

Хук usePosition() готов к использованию.


Напоследок


Вы можете найти демонстрацию работы хука и более детальную его имплементацию с возможностью задания параметров отслеживания на GitHub.


Я надеюсь эта статья была полезной для вас. Успешного кодинга!

Tags:
Hubs:
Total votes 13: ↑11 and ↓2+9
Comments1

Articles