#react-native #geolocation #react-hooks #react-functional-component #use-ref
#react-native #геолокация #реагирующие крючки #реагирующий функциональный компонент #использовать-ссылка
Вопрос:
Я пытаюсь создать простой пример отслеживания местоположения, и я застрял в следующем случае. Моя основная цель — переключить просмотр местоположения, нажав кнопку start / end. Я разделяю проблемы, внедряя пользовательский реагирующий хук, который затем используется в компоненте приложения:
useWatchLocation.js
import {useEffect, useRef, useState} from "react"
import {PermissionsAndroid} from "react-native"
import Geolocation from "react-native-geolocation-service"
const watchCurrentLocation = async (successCallback, errorCallback) => {
if (!(await PermissionsAndroid.check(PermissionsAndroid.PERMISSIONS.ACCESS_FINE_LOCATION))) {
errorCallback("Permissions for location are not granted!")
}
return Geolocation.watchPosition(successCallback, errorCallback, {
timeout: 3000,
maximumAge: 500,
enableHighAccuracy: true,
distanceFilter: 0,
useSignificantChanges: false,
})
}
const stopWatchingLocation = (watchId) => {
Geolocation.clearWatch(watchId)
// Geolocation.stopObserving()
}
export default useWatchLocation = () => {
const [location, setLocation] = useState()
const [lastError, setLastError] = useState()
const [locationToggle, setLocationToggle] = useState(false)
const watchId = useRef(null)
const startLocationWatch = () => {
watchId.current = watchCurrentLocation(
(position) => {
setLocation(position)
},
(error) => {
setLastError(error)
}
)
}
const cancelLocationWatch = () => {
stopWatchingLocation(watchId.current)
setLocation(null)
setLastError(null)
}
const setLocationWatch = (flag) => {
setLocationToggle(flag)
}
// execution after render when locationToggle is changed
useEffect(() => {
if (locationToggle) {
startLocationWatch()
} else cancelLocationWatch()
return cancelLocationWatch()
}, [locationToggle])
// mount / unmount
useEffect(() => {
cancelLocationWatch()
}, [])
return { location, lastError, setLocationWatch }
}
App.js
import React from "react"
import {Button, Text, View} from "react-native"
import useWatchLocation from "./hooks/useWatchLocation"
export default App = () => {
const { location, lastError, setLocationWatch } = useWatchLocation()
return (
<View style={{ margin: 20 }}>
<View style={{ margin: 20, alignItems: "center" }}>
<Text>{location amp;amp; `Time: ${new Date(location.timestamp).toLocaleTimeString()}`}</Text>
<Text>{location amp;amp; `Latitude: ${location.coords.latitude}`}</Text>
<Text>{location amp;amp; `Longitude: ${location.coords.longitude}`}</Text>
<Text>{lastError amp;amp; `Error: ${lastError}`}</Text>
</View>
<View style={{ marginTop: 20, width: "100%", flexDirection: "row", justifyContent: "space-evenly" }}>
<Button onPress={() => {setLocationWatch(true)}} title="START" />
<Button onPress={() => {setLocationWatch(false)}} title="STOP" />
</View>
</View>
)
}
Я искал несколько примеров, которые есть в Сети, и приведенный выше код должен работать. Но проблема в том, что при нажатии кнопки остановки местоположение по-прежнему обновляется, даже если я вызываю Geolocation.clearWatch (watchId).
Я завернул вызовы геолокации для обработки разрешения местоположения и других возможных отладочных материалов. Похоже, что значение watchId, сохраненное с помощью useRef хук внутри useWatchLocation, недопустимо. Мое предположение основано на попытке вызвать Geolocation.stopObserving() сразу после Geolocation.clearWatch(watchId). Подписка прекращается, но я получаю предупреждение:
Вызывается stopObserving с существующими подписками.
Поэтому я предполагаю, что исходная подписка не была очищена.
Что я упускаю / делаю неправильно?
РЕДАКТИРОВАТЬ: я нашел решение. Но поскольку смонтированный шаблон обычно считается антишаблоном: у кого-нибудь есть лучшее решение?
Ответ №1:
Хорошо, проблема решена с помощью смонтированного шаблона. Смонтировано.значение current установлено в locationToggle
значение effect true
, а внутри cancelLocationWatch
— в false
:
const isMounted = useRef(null)
...
useEffect(() => {
if (locationToggle) {
isMounted.current = true // <--
startLocationWatch()
} else cancelLocationWatch()
return () => cancelLocationWatch()
}, [locationToggle])
...
const cancelLocationWatch = () => {
stopWatchingLocation(watchId.current)
setLocation(null)
setLastError(null)
isMounted.current = false // <--
}
И проверяется при эффекте монтирования / размонтирования, успешном и ошибочном обратном вызове:
const startLocationWatch = () => {
watchId.current = watchCurrentLocation(
(position) => {
if (isMounted.current) { // <--
setLocation(position)
}
},
(error) => {
if (isMounted.current) { // <--
setLastError(error)
}
}
)
}
Комментарии:
1. Я думаю, что это оправданный вариант использования
isMounted
шаблона