Expo-Location无法正确存储位置至数组,Redux读取为空求助
问题描述
在React Native应用中使用expo-location追踪用户位置,将位置存入Redux对象后,调试时能看到位置已成功插入数组,但读取该数组时始终为空——onNewPosition函数里的positions变量一直是空数组,改用本地state也遇到相同问题。
组件代码(Run.js)
import { StyleSheet, Text, View } from 'react-native' import React from 'react' import MapView, { Polyline, Marker } from 'react-native-maps'; import { useEffect, useState, useRef } from 'react'; import Monitor from './Monitor'; import * as turf from '@turf/turf'; import Pin from './Pin'; import * as Location from 'expo-location'; import { useSelector, useDispatch } from 'react-redux'; import { addPosition, positionsData } from '../../reduxThings/positionSlice'; const Run = ({distance, latitude, longitude}) => { const mapRef = useRef(null); const [stateDistance, setStateDistance] = useState(0); const [statePace, setStatePace] = useState(0); const [duration, setDuration] = useState(0); const [coordinates, setCoordinates] = useState([]); const [currentPosition, setCurrentPosition] = useState({coords: {latitude, longitude}}); const positions = useSelector(positionsData); const dispatch = useDispatch(); console.log(positions); const onNewPosition = (position) => { mapRef?.current?.animateToRegion(position.coords, 1000); const durr = positions.length>0 ? position.timestamp - positions[0].timestamp : 0; const dist = positions.length>0 ?distanceBetween(positions[positions.length - 1], position):0; const pace = positions.length>0 ?paceBetween(dist, positions[positions.length - 1], position):0; setCurrentPosition(position); setDuration(durr); dispatch(addPosition(position)); setStateDistance(dist); setStatePace(pace); setCoordinates([...coordinates, {"latitude": position.coords.latitude, "longitude": position.coords.longitude}]); } useEffect(() => { (async () => { const options = { enableHighAccuracy: true, timeInterval: 1000, distanceInterval: 0 }; let locations = await Location.watchPositionAsync(options,onNewPosition); })(); }, []); const distanceBetween = (origin, destination) => { const from = turf.point([origin.coords.longitude, origin.coords.latitude]); const to = turf.point([destination.coords.longitude, destination.coords.latitude]); const options = {units: 'meters'}; return Math.round(turf.distance(from, to, options)); } const paceBetween = (del, pp, p) =>{ const time = (p.timestamp - pp.timestamp); return time / del; } return ( <View style={styles.container}> <Monitor distance={stateDistance} pace={statePace}/> <MapView ref={mapRef} initialRegion={{latitude, longitude, latitudeDelta: 0.001, longitudeDelta:0.01}} provider="google" style={styles.map}> <Marker coordinate={currentPosition.coords}> <Pin/> </Marker> <Polyline coordinates={coordinates} strokeColor="#f2b659" strokeWidth={10}/> </MapView> </View> ) } export default Run const styles = StyleSheet.create({ container: { flex: 1, }, map: { flex: 1, }, })
Redux Slice代码(positionSlice.js)
import { createSlice } from '@reduxjs/toolkit'; export const positionSlice = createSlice({ name: "position", initialState: { positions: [] }, reducers: { addPosition: (state, action) => { state.positions = [...state.positions, action.payload]; console.log(state.positions); } } }) export const { addPosition } = positionSlice.actions; export const positionsData = (state) => state.position.positions; export default positionSlice.reducer;
解决方案
核心问题:闭包导致的Stale State
useEffect的依赖数组为空,意味着watchPositionAsync仅在组件挂载时执行一次,此时创建的onNewPosition函数捕获的是组件初始状态下的空数组positions。后续组件虽因Redux状态更新重新渲染,但onNewPosition不会重新创建,始终使用第一次捕获的旧值,所以看到positions一直为空。
修复步骤
1. 用useCallback包裹onNewPosition并更新依赖
确保onNewPosition能捕获最新的Redux状态,同时避免不必要的重渲染:
import { useCallback, useStore } from 'react'; // ...其他代码不变 const onNewPosition = useCallback((position) => { mapRef?.current?.animateToRegion(position.coords, 1000); // 直接从Redux Store获取最新的positions const latestPositions = useStore().getState().position.positions; const durr = latestPositions.length > 0 ? position.timestamp - latestPositions[0].timestamp : 0; const dist = latestPositions.length > 0 ? distanceBetween(latestPositions.at(-1), position) : 0; const pace = latestPositions.length > 0 ? paceBetween(dist, latestPositions.at(-1), position) : 0; setCurrentPosition(position); setDuration(durr); dispatch(addPosition(position)); setStateDistance(dist); setStatePace(pace); // 本地state用函数式更新,确保拿到最新值 setCoordinates(prev => [...prev, { latitude: position.coords.latitude, longitude: position.coords.longitude }]); }, [dispatch, mapRef]); // dispatch和mapRef是稳定值,不会触发不必要重渲染
2. 更新useEffect的依赖数组
将onNewPosition加入依赖,同时添加清理函数避免内存泄漏:
useEffect(() => { (async () => { const options = { enableHighAccuracy: true, timeInterval: 1000, distanceInterval: 0 }; const locations = await Location.watchPositionAsync(options, onNewPosition); // 组件卸载时取消位置监听 return () => locations.remove(); })(); }, [onNewPosition]);
3. 可选:将计算逻辑移到Redux Reducer
把距离、配速等计算逻辑集中到Redux,让组件只负责渲染和触发action,进一步避免状态同步问题:
// positionSlice.js import { createSlice } from '@reduxjs/toolkit'; import * as turf from '@turf/turf'; export const positionSlice = createSlice({ name: "position", initialState: { positions: [], totalDistance: 0, duration: 0, currentPace: 0 }, reducers: { addPosition: (state, action) => { const newPos = action.payload; if (state.positions.length === 0) { state.positions.push(newPos); return; } const lastPos = state.positions.at(-1); // 计算距离 const from = turf.point([lastPos.coords.longitude, lastPos.coords.latitude]); const to = turf.point([newPos.coords.longitude, newPos.coords.latitude]); const dist = Math.round(turf.distance(from, to, { units: 'meters' })); // 计算配速和时长 const timeDiff = newPos.timestamp - lastPos.timestamp; state.positions.push(newPos); state.totalDistance += dist; state.duration = newPos.timestamp - state.positions[0].timestamp; state.currentPace = timeDiff / dist; } } }) export const { addPosition } = positionSlice.actions; export const positionsData = (state) => state.position.positions; export const totalDistance = (state) => state.position.totalDistance; export const totalDuration = (state) => state.position.duration; export const currentPace = (state) => state.position.currentPace; export default positionSlice.reducer;
组件简化后:
const Run = ({latitude, longitude}) => { const mapRef = useRef(null); const positions = useSelector(positionsData); const totalDistance = useSelector(totalDistance); const currentPace = useSelector(currentPace); const [currentPosition, setCurrentPosition] = useState({coords: {latitude, longitude}}); const dispatch = useDispatch(); const onNewPosition = useCallback((position) => { mapRef?.current?.animateToRegion(position.coords, 1000); setCurrentPosition(position); dispatch(addPosition(position)); }, [dispatch, mapRef]); useEffect(() => { (async () => { const options = { enableHighAccuracy: true, timeInterval: 1000, distanceInterval: 0 }; const locations = await Location.watchPositionAsync(options, onNewPosition); return () => locations.remove(); })(); }, [onNewPosition]); const coordinates = positions.map(p => ({ latitude: p.coords.latitude, longitude: p.coords.longitude })); return ( <View style={styles.container}> <Monitor distance={totalDistance} pace={currentPace}/> <MapView ref={mapRef} initialRegion={{latitude, longitude, latitudeDelta: 0.001, longitudeDelta:0.01}} provider="google" style={styles.map}> <Marker coordinate={currentPosition.coords}> <Pin/> </Marker> <Polyline coordinates={coordinates} strokeColor="#f2b659" strokeWidth={10}/> </MapView> </View> ) }
内容的提问来源于stack exchange,提问作者Abdelmouheimen Trabelssi
相关产品推荐
相关产品推荐

