React Native定位数组推送报错:TypeError无法添加新属性
React Native 位置追踪数组Push报错排查与修复
问题概述
开发React Native应用用于追踪用户位置并将坐标存入数组绘制路线时,执行push操作触发错误:
TypeError: cannot add a new property, js engine: hermes
该功能此前正常,尝试三种实现方式(CASE1、CASE2、CASE3)均无效,偶现无报错但数组始终为空的情况,仅单独页面的CASE4可正常运行。
各实现方案问题分析
CASE 1:组件内普通变量存储
import React, {useEffect} from 'react'; import {Alert, StyleSheet, TouchableOpacity, View} from 'react-native'; import Geolocation from 'react-native-geolocation-service'; import {moderateScale} from 'react-native-size-matters'; import Entypo from 'react-native-vector-icons/Entypo'; import {MainRoutes} from '../constants/Routes'; import colors from '../constants/theme'; const GpsTrackerScreen = ({navigation}) => { let location_route = []; useEffect(() => { watchPosition(); }, []); const watchPosition = () => { try { const watchID = Geolocation.watchPosition( position => { let coords = { latitude: position.coords.latitude, longitude: position.coords.longitude, }; location_route.push(coords); }, error => Alert.alert('WatchPosition Error', JSON.stringify(error)), ); } catch (error) { Alert.alert('WatchPosition Error', JSON.stringify(error)); } }; return ( <View style={styles.container}> <TouchableOpacity onPress={() => navigation.navigate(MainRoutes.TrackHistoryScreen)} style={styles.HistoryButton}> <Entypo name={'back-in-time'} color={'#fff'} size={moderateScale(30)} /> </TouchableOpacity> </View> ); }; export default GpsTrackerScreen; const styles = StyleSheet.create({ container: {flex: 1, backgroundColor: colors.white, alignItems: 'center'}, HistoryButton: { width: moderateScale(50), position: 'absolute', top: 20, right: 20, zIndex: 200, alignSelf: 'center', height: moderateScale(50), justifyContent: 'center', paddingHorizontal: moderateScale(10), alignItems: 'center', elevation: 5, borderRadius: moderateScale(50), backgroundColor: colors.primaryColor, }, });
问题:location_route是组件内普通变量,组件重渲染时会被重置为空数组。同时Hermes引擎可能将这类变量优化为不可变对象,导致无法执行push操作。
CASE 2:useState存储但存在闭包问题
import React, {useEffect, useState} from 'react'; import {Alert, StyleSheet, TouchableOpacity, View} from 'react-native'; import Geolocation from 'react-native-geolocation-service'; import {moderateScale} from 'react-native-size-matters'; import Entypo from 'react-native-vector-icons/Entypo'; import fonts from '../constants/fonts'; import {MainRoutes} from '../constants/Routes'; import {SCREEN_WIDTH} from '../constants/scaling'; import colors from '../constants/theme'; const GpsTrackerScreen = ({navigation}) => { const [location_route, setLocationRoute] = useState([]); useEffect(() => { watchPosition(); }, []); const watchPosition = () => { try { const watchID = Geolocation.watchPosition( position => { let coords = { latitude: position.coords.latitude, longitude: position.coords.longitude, }; let temp_route = [...location_route]; temp_route.push(coords); setLocationRoute(temp_route); }, error => Alert.alert('WatchPosition Error', JSON.stringify(error)), ); } catch (error) { Alert.alert('WatchPosition Error', JSON.stringify(error)); } }; return ( <View style={styles.container}> <TouchableOpacity onPress={() => navigation.navigate(MainRoutes.TrackHistoryScreen)} style={styles.HistoryButton}> <Entypo name={'back-in-time'} color={'#fff'} size={moderateScale(30)} /> </TouchableOpacity> </View> ); }; export default GpsTrackerScreen; const styles = StyleSheet.create({ container: {flex: 1, backgroundColor: colors.white, alignItems: 'center'}, HistoryButton: { width: moderateScale(50), position: 'absolute', top: 20, right: 20, zIndex: 200, alignSelf: 'center', height: moderateScale(50), justifyContent: 'center', paddingHorizontal: moderateScale(10), alignItems: 'center', elevation: 5, borderRadius: moderateScale(50), backgroundColor: colors.primaryColor, }, });
问题:useEffect依赖数组为空,watchPosition内引用的location_route始终是初始空数组,每次更新都会基于空数组添加新坐标,导致数组仅保留最后一个值,且闭包问题导致无法获取最新状态。
CASE 3:全局变量存储
import React, {useEffect} from 'react'; import {Alert, StyleSheet, TouchableOpacity, View} from 'react-native'; import Geolocation from 'react-native-geolocation-service'; import {moderateScale} from 'react-native-size-matters'; import Entypo from 'react-native-vector-icons/Entypo'; import {MainRoutes} from '../constants/Routes'; import colors from '../constants/theme'; let location_route = []; const GpsTrackerScreen = ({navigation}) => { useEffect(() => { watchPosition(); }, []); const watchPosition = () => { try { const watchID = Geolocation.watchPosition( position => { let coords = { latitude: position.coords.latitude, longitude: position.coords.longitude, }; location_route.push(coords); }, error => Alert.alert('WatchPosition Error', JSON.stringify(error)), ); } catch (error) { Alert.alert('WatchPosition Error', JSON.stringify(error)); } }; return ( <View style={styles.container}> <TouchableOpacity onPress={() => navigation.navigate(MainRoutes.TrackHistoryScreen)} style={styles.HistoryButton}> <Entypo name={'back-in-time'} color={'#fff'} size={moderateScale(30)} /> </TouchableOpacity> </View> ); }; export default GpsTrackerScreen; const styles = StyleSheet.create({ container: {flex: 1, backgroundColor: colors.white, alignItems: 'center'}, HistoryButton: { width: moderateScale(50), position: 'absolute', top: 20, right: 20, zIndex: 200, alignSelf: 'center', height: moderateScale(50), justifyContent: 'center', paddingHorizontal: moderateScale(10), alignItems: 'center', elevation: 5, borderRadius: moderateScale(50), backgroundColor: colors.primaryColor, }, });
问题:全局变量不会随组件卸载重置,易引发内存泄漏;组件多次挂载时会重复向同一数组添加坐标。此外Hermes严格模式下,全局对象属性可能被标记为不可扩展,导致push失败。
CASE 4:组件内变量+手动触发
import React, {useEffect, useState} from 'react'; import {Alert, StyleSheet, TouchableOpacity, View} from 'react-native'; import Geolocation from 'react-native-geolocation-service'; import {moderateScale} from 'react-native-size-matters'; import SplashScreen from 'react-native-splash-screen'; import Entypo from 'react-native-vector-icons/Entypo'; const GpsTrackerScreen = ({navigation}) => { let location_route = []; const [watchid, setWatchId] = useState(null); SplashScreen.hide(); const clearwatch = () => { Geolocation.clearWatch(watchid); }; const watchPosition = () => { try { const watchID = Geolocation.watchPosition( position => { let coords = { latitude: position.coords.latitude, longitude: position.coords.longitude, }; location_route.push(coords); console.log(location_route, 'location_route'); }, error => Alert.alert('WatchPosition Error', JSON.stringify(error)), { accuracy: { android: 'high', ios: 'best', }, enableHighAccuracy: false, timeout: 15000, maximumAge: 10000, distanceFilter: 0, forceRequestLocation: true, forceLocationManager: true, showLocationDialog: true, }, ); setWatchId(watchID); } catch (error) { Alert.alert('WatchPosition Error', JSON.stringify(error)); } }; return ( <View style={styles.container}> <TouchableOpacity onPress={() => watchPosition()} style={styles.HistoryButton}> <Entypo name={'back-in-time'} color={'#fff'} size={moderateScale(30)} /> </TouchableOpacity> <TouchableOpacity onPress={() => clearwatch()} style={styles.HistoryButton}> <Entypo name={'back-in-time'} color={'#fff'} size={moderateScale(30)} /> </TouchableOpacity> </View> ); }; export default GpsTrackerScreen; const styles = StyleSheet.create({ container: {flex: 1, backgroundColor: '#fff', alignItems: 'center'}, HistoryButton: { width: moderateScale(50), zIndex: 200, alignSelf: 'center', height: moderateScale(50), justifyContent: 'center', paddingHorizontal: moderateScale(10), alignItems: 'center', elevation: 5, margin: 100, borderRadius: moderateScale(50), backgroundColor: 'green', }, });
正常运行原因:location_route在watchPosition手动触发时创建,回调持有变量引用,且组件未因状态更新重渲染,变量不会被重置。但此方式无法响应数组变化更新UI,仅适合调试。
修复方案
方案1:useState函数式更新(推荐,支持UI更新)
import React, {useEffect, useState} from 'react'; import {Alert, StyleSheet, TouchableOpacity, View} from 'react-native'; import Geolocation from 'react-native-geolocation-service'; import {moderateScale} from 'react-native-size-matters'; import Entypo from 'react-native-vector-icons/Entypo'; import {MainRoutes} from '../constants/Routes'; import colors from '../constants/theme'; const GpsTrackerScreen = ({navigation}) => { const [location_route, setLocationRoute] = useState([]); let watchID = null; useEffect(() => { watchPosition(); // 组件卸载时清除定位监听,防止内存泄漏 return () => { if (watchID) Geolocation.clearWatch(watchID); }; }, []); const watchPosition = () => { try { watchID = Geolocation.watchPosition( position => { const coords = { latitude: position.coords.latitude, longitude: position.coords.longitude, }; // 函数式更新,确保基于最新数组状态添加坐标 setLocationRoute(prevRoute => [...prevRoute, coords]); }, error => Alert.alert('WatchPosition Error', JSON.stringify(error)), { accuracy: {android: 'high', ios: 'best'}, enableHighAccuracy: false, timeout: 15000, maximumAge: 10000, distanceFilter: 0, forceRequestLocation: true, forceLocationManager: true, showLocationDialog: true, } ); } catch (error) { Alert.alert('WatchPosition Error', JSON.stringify(error)); } }; return ( <View style={styles.container}> <TouchableOpacity onPress={() => navigation.navigate(MainRoutes.TrackHistoryScreen)} style={styles.HistoryButton}> <Entypo name={'back-in-time'} color={'#fff'} size={moderateScale(30)} /> </TouchableOpacity> </View> ); }; export default GpsTrackerScreen; const styles = StyleSheet.create({ container: {flex: 1, backgroundColor: colors.white, alignItems: 'center'}, HistoryButton: { width: moderateScale(50), position: 'absolute', top: 20, right: 20, zIndex: 200, alignSelf: 'center', height: moderateScale(50), justifyContent: 'center', paddingHorizontal: moderateScale(10), alignItems: 'center', elevation: 5, borderRadius: moderateScale(50), backgroundColor: colors.primaryColor, }, });
核心改进:
- 使用函数式更新
setLocationRoute(prevRoute => [...prevRoute, coords]),避免闭包导致的状态过期问题 - 添加组件卸载时的监听清理逻辑,防止内存泄漏
- 配置完整的定位参数,确保定位回调稳定触发
方案2:useRef存储数组(无需UI更新时使用)
若仅需存储坐标、无需更新UI,可使用useRef,其.current属性不会随组件渲染重置:
import React, {useEffect, useRef} from 'react'; import {Alert, StyleSheet, TouchableOpacity, View} from 'react-native'; import Geolocation from 'react-native-geolocation-service'; import {moderateScale} from 'react-native-size-matters'; import Entypo from 'react-native-vector-icons/Entypo'; import {MainRoutes} from '../constants/Routes'; import colors from '../constants/theme'; const GpsTrackerScreen = ({navigation}) => { const location_route = useRef([]); let watchID = null; useEffect(() => { watchPosition(); return () => { if (watchID) Geolocation.clearWatch(watchID); }; }, []); const watchPosition = () => { try { watchID = Geolocation.watchPosition( position => { const coords = { latitude: position.coords.latitude, longitude: position.coords.longitude, }; location_route.current.push(coords); // 通过location_route.current获取完整坐标数组 }, error => Alert.alert('WatchPosition Error', JSON.stringify(error)), { accuracy: {android: 'high', ios: 'best'}, enableHighAccuracy: false, timeout: 15000, maximumAge: 10000, distanceFilter: 0, forceRequestLocation: true, forceLocationManager: true, showLocationDialog: true, } ); } catch (error) { Alert.alert('WatchPosition Error', JSON.stringify(error)); } }; return ( <View style={styles.container}> <TouchableOpacity onPress={() => navigation.navigate(MainRoutes.TrackHistoryScreen)} style={styles.HistoryButton}> <Entypo name={'back-in-time'} color={'#fff'} size={moderateScale(30)} /> </TouchableOpacity> </View> ); }; export default GpsTrackerScreen; const styles = StyleSheet.create({ container: {flex: 1, backgroundColor: colors.white, alignItems: 'center'}, HistoryButton: { width: moderateScale(50), position: 'absolute', top: 20, right: 20, zIndex: 200, alignSelf: 'center', height: moderateScale(50), justifyContent: 'center', paddingHorizontal: moderateScale(10), alignItems: 'center', elevation: 5, borderRadius: moderateScale(50), backgroundColor: colors.primaryColor, }, });
额外排查点
- 检查React Native与
react-native-geolocation-service版本兼容性,近期版本可能对Hermes引擎做了调整 - 确认设备位置权限是否正常,权限拒绝会导致定位回调不触发,表现为数组为空
- 清除项目缓存:执行
npm start --reset-cache或yarn start --reset-cache,缓存异常可能引发变量
相关产品推荐
相关产品推荐

