React组件切换时报无法对已卸载组件更新状态警告如何解决
问题修复方案
核心问题原因
- 你在Photo组件中优先调用
props.onChange(false)触发父组件切换回Game,直接卸载了当前Photo组件,后续异步请求的回调、状态更新操作就会触发「卸载组件更新状态」警告 - Game组件中的位置监听
watchPositionAsync未配置卸载清理函数,组件卸载后仍会触发位置更新的状态修改 - 直接对state中的
targets数组调用splice修改原数组,属于状态突变,会引发不可预期的渲染异常 - Photo组件的return中直接写
Alert.alert,每次渲染都会重复触发弹窗,逻辑错误
修复代码
1. Photo.js 全量修改
import React, { useState, useEffect, useRef } from 'react'; import { StyleSheet, Text, View, Alert, Button, Image } from 'react-native'; import * as ImagePicker from 'expo-image-picker'; import * as Permissions from 'expo-permissions'; import * as Location from 'expo-location'; export default function PhotoHandler(props) { const [pickedLocation, setPickedLocation] = useState({ latitude: null, longitude: null }); const [pickedImage, setPickedImage] = useState(null); // 挂载状态标记,避免卸载后执行状态更新 const isMounted = useRef(true); useEffect(() => { // 组件挂载时触发弹窗,不要写在return中避免重复触发 Alert.alert( "Success", "Take photo", [ {text: "Open Camera", onPress: imagetakenHandler } ] ) // 卸载时更新挂载标记 return () => { isMounted.current = false; } }, []) const verifyPermissions = async () =>{ const result = await Permissions.askAsync(Permissions.CAMERA_ROLL, Permissions.CAMERA); if(result.status != 'granted'){ Alert.alert( 'Insufficient Permissions!', 'You need to grant camera permissions to use this app', [{text: 'Okay'}] ); return false; } return true; }; const imagetakenHandler = async () =>{ if(!isMounted.current) return const hasPermission = await verifyPermissions(); if(!hasPermission) return; const image = await ImagePicker.launchCameraAsync({ mediaTypes: ImagePicker.MediaTypeOptions.Images, quality: 1, aspect: [1, 1], base64: true, exif: true }); if(!image.cancelled && isMounted.current){ setPickedImage(image.base64); } }; useEffect(() => { const requestOptions = { method: 'POST', headers: {'Content-type': 'application/x-www-form-urlencoded'}, }; if(pickedImage != null && isMounted.current){ // 先完成上传请求,所有操作结束后再切换组件 fetch(`https://xxx.yy.zz`, requestOptions) .then(response => response.json()) .then(res => { console.log('SUCCESS: '+res) if(isMounted.current) props.onChange(false); }) .catch(e => { console.log(e); // 报错也可切换回Game,可根据业务需求调整 if(isMounted.current) props.onChange(false); }); } }, [pickedImage]) return ( <View style={styles.container}> </View> ); } const styles = StyleSheet.create({ container: { flex: 1, marginTop: 30 } });
2. Game.js 关键逻辑修改
// 位置监听的useEffect新增清理函数,卸载时停止位置监听 useEffect(() => { console.log("Inside UseEffect watch position"); let locationSubscription = null; (async () => { try { locationSubscription = await Location.watchPositionAsync( { accuracy: Location.Accuracy.BestForNavigation, distanceInterval: 10, timeInterval: 3000, }, (loc) => { setPickedLocation({ latitude: loc.coords.latitude, longitude: loc.coords.longitude }); } ); } catch (e) { Alert.alert("Error"); } })(); // 组件卸载时清理监听 return () => { if(locationSubscription) locationSubscription.remove(); } }, []); // 计算距离的useEffect修正targets状态修改逻辑,避免直接修改原state useEffect(() => { console.log('inside useEffect hook for measuring targets distances'); var lat1 = pickedLocation.latitude; var lng1 = pickedLocation.longitude; console.log("targets distance"); const R = 6371; let targets_distance = []; // 浅拷贝targets,不直接修改原状态数组 let newTargets = [...targets]; let shouldSwitchToPhoto = false; for (i = 0; i < newTargets.length; i++) { const φ1 = lat1 * Math.PI / 180; const φ2 = newTargets[i].lat * Math.PI / 180; const Δφ = (newTargets[i].lat - lat1) * Math.PI / 180; const Δλ = (newTargets[i].lng - lng1) * Math.PI / 180; const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) + Math.cos(φ1) * Math.cos(φ2) * Math.sin(Δλ / 2) * Math.sin(Δλ / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); const distance = R * c * 1000; console.log('distance is'+ distance); if(distance <=20){ shouldSwitchToPhoto = true; newTargets.splice(i, 1); i--; // 数组长度变化后修正索引避免漏遍历 console.log("targets after removal"); console.log(newTargets); } else{ targets_distance.push(distance); } } // 同步更新targets状态 setTargets(newTargets); if(shouldSwitchToPhoto){ setConditionCounter(true); //切换到photo组件 } console.log(targets_distance); var index = 0; var value = targets_distance[0]; for (var i = 1; i < targets_distance.length; i++) { if (targets_distance[i] < value) { value = targets_distance[i]; index = i; } } console.log("smallest element is " + value + " at index " + index); setIndex(index); setDistances(targets_distance); }, [pickedLocation]);
内容的提问来源于stack exchange,提问作者Meana
相关产品推荐
相关产品推荐

