Expo React Native应用遭遇Too many re-renders错误求解决
问题分析与修复
核心错误原因
- Firebase监听直接写在组件体内:
onChildAdded直接放在组件函数里,每次组件渲染都会重新注册监听,回调里调用setOtherRiders触发重渲染,形成无限循环。 - useEffect无依赖数组:你的
useEffect没有指定依赖项,导致每次组件渲染都会执行requestLocationPermission,如果这个函数里调用了setMyCurrentLocation或setIsMapReady,就会再次触发重渲染,循环往复。
修复步骤
1. 将Firebase监听移入useEffect并清理监听
把onChildAdded的注册逻辑放到useEffect里,并且返回清理函数移除监听,避免重复注册。同时注意数据格式,确保otherRiders是数组类型。
2. 给useEffect添加正确的依赖数组
requestLocationPermission的useEffect只需要在组件挂载时执行一次,所以给它加空依赖数组[];如果需要根据myCurrentLocation变化重新执行,再添加对应依赖,但这里应该只初始化一次。
3. 渲染其他骑手的标记
在MapView里遍历otherRiders渲染对应的Marker。
修改后的完整代码
import React, { useState, useEffect } from "react"; import { View, Text, ScrollView, StyleSheet, TextInput, Button, Switch, Dimensions, Image } from "react-native"; import { useNavigation } from '@react-navigation/native'; import MapView, { Marker, Polyline } from 'react-native-maps'; import { requestLocationPermission, getCurrentLocation, writeMyLocationData } from '../../sockets/googlemap' import { findPlace, fetchRoute } from '../../sockets/googleroute' import { ref, onChildAdded, off } from "firebase/database"; import { database } from '../../firebaseConfig'; const Main = () => { const [otherRiders, setOtherRiders] = useState([]); const [routeInfo, setRouteInfo] = useState({ origin: "", destination: "" }); const navigation = useNavigation(); const [myCurrentLocation, setMyCurrentLocation] = useState({ latitude: 0, longitude: 0, }) const [myDestination, setMyDestination] = useState({ latitude: 0, longitude: 0, }) const [myOrigin, setMyOrigin] = useState({ latitude: 0, longitude: 0, }) const [isMapReady, setIsMapReady] = useState(false); const [isEnabled, setIsEnabled] = useState(false); const toggleSwitch = () => setIsEnabled(previousState => !previousState); const iconImage = require('../../assets/kurumi.jpg'); const myDestinationIcon = require('../../assets/flag.png'); const riderIcon = require('../../assets/your-rider-icon.png'); // 替换成你的骑手图标 // 初始化位置权限和监听Firebase数据 useEffect(() => { // 请求位置权限 requestLocationPermission(setMyCurrentLocation, myCurrentLocation, setIsMapReady); // Firebase数据监听 const dbref = ref(database); // 确保这里是正确的路径,比如'/riders' const handleChildAdded = (snapshot) => { let data = snapshot.val(); // 确保数据是数组格式,如果是单个对象就转成数组 const newRiders = Array.isArray(data) ? data : [data]; setOtherRiders(prev => [...prev, ...newRiders]); console.log("获取到骑手数据:", newRiders); }; onChildAdded(dbref, handleChildAdded); // 清理函数:组件卸载时移除监听 return () => { off(dbref, 'child_added', handleChildAdded); }; }, []); // 空依赖数组,只执行一次 return ( isMapReady ? ( <ScrollView style={styles.Wrapper}> <View style={styles.mapContainer}> <MapView style={styles.map} initialRegion={{ latitude: myCurrentLocation.latitude, longitude: myCurrentLocation.longitude, latitudeDelta: 0.0922, longitudeDelta: 0.0421, }} > {/* 自己的位置标记 */} <Marker coordinate={{ latitude: myCurrentLocation.latitude, longitude: myCurrentLocation.longitude, }} > <Image style={styles.icon} source={iconImage} /> </Marker> {/* 目的地标记 */} <Marker coordinate={{ latitude: myDestination.latitude, longitude: myDestination.longitude, }} > <Image style={styles.icon} source={myDestinationIcon} /> </Marker> {/* 其他骑手的标记 */} {otherRiders.map((rider, index) => ( <Marker key={rider.user_id || index} // 优先用user_id当key,无则用index coordinate={{ latitude: rider.latitude, longitude: rider.longitude, }} > <Image style={styles.icon} source={riderIcon} /> </Marker> ))} {/* 路线 */} <Polyline coordinates={[ { latitude: myOrigin.latitude, longitude: myOrigin.longitude }, { latitude: myDestination.latitude, longitude: myDestination.longitude }, ]} strokeColor="#FF0000" strokeWidth={2} /> </MapView> </View> <View> <Text style={styles.direction}>出発地</Text> <TextInput placeholder={'出発地を入力してください'} value={routeInfo.origin} autoCapitalize="none" onChangeText={text => setRouteInfo(prev => ({ ...prev, origin: text }))} style={styles.imputBox} /> </View> <View> <Text style={styles.direction}>目的地</Text> <TextInput placeholder={'到着地を入力してください'} value={routeInfo.destination} autoCapitalize="none" onChangeText={text => setRouteInfo(prev => ({ ...prev, destination: text }))} style={styles.imputBox} /> </View> <View> <Button title="ルートを検索する" onPress={() => fetchRoute(setMyOrigin, setMyDestination, routeInfo)} /> </View> <View> <Text style={styles.infomation}>位置情報共有をONにする</Text> <Switch trackColor={{ false: '#767577', true: '#81b0ff' }} thumbColor={isEnabled ? '#f5dd4b' : '#f4f3f4'} ios_backgroundColor="#3e3e3e" onValueChange={toggleSwitch} value={isEnabled} /> </View> <View> <Button title="戻る" onPress={() => navigation.navigate('Top')} /> </View> </ScrollView> ) : ( <ScrollView style={styles.waitScrollViewContent}> <View style={styles.center}> <Text style={styles.infomation}>少々お待ちください。。。</Text> </View> </ScrollView> ) ) } export default Main; const { height } = Dimensions.get("window"); const mapHeight = height * 0.5; const styles = StyleSheet.create({ Wrapper: { flex: 1, }, direction: { fontSize: 12, padding: 10 }, infomation: { fontSize: 12, padding: 10 }, subTitle: { fontSize: 14, textAlign: "center", padding: 5 }, mapContainer: { height: mapHeight, justifyContent: 'center', alignItems: 'center', }, map: { ...StyleSheet.absoluteFillObject, }, markerContainer: { backgroundColor: 'blue', padding: 5, borderRadius: 5, }, markerText: { color: 'white', fontWeight: 'bold', }, imputWrapper: { alignItems: "center", padding: 20 }, imputContainer: { width: '100%', marginBottom: 10 }, imputBox: { height: 40, borderWidth: 1, }, icon: { width: 28, height: 28, }, waitScrollViewContent: { flex: 1, width: '100%', }, center: { flex: 1, justifyContent: 'center', alignItems: 'center', }, });
额外注意点
- 确保Firebase的
ref路径正确,比如如果骑手数据存在/riders节点下,要写成ref(database, 'riders'),不然会监听整个数据库的所有变化。 - 如果
requestLocationPermission内部会频繁更新myCurrentLocation,可以考虑在useEffect里添加对应的依赖,但要注意避免无限循环,必要时用useCallback包裹函数。
内容的提问来源于stack exchange,提问作者谷口祐人
相关产品推荐
相关产品推荐

