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