You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.28 15:09:58