Если вкратце
В этой статье мы создадим 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.
Я надеюсь эта статья была полезной для вас. Успешного кодинга!