React Native首次切换Tab触发未挂载组件状态更新警告
问题根因
这个警告是两个代码写法问题叠在一起触发的:
- BottomTabNavigator 默认采用懒加载机制:首次切换到Done页面前,Done页面及内部所有列表项组件都未挂载,只有第一次进入时才会完成首次渲染。
SingleTodo组件存在冗余的本地状态:你把父组件传入的done属性额外用useState存了一份本地状态,而且在AsyncStorage.setItem的异步回调里调用本地setDone更新状态。当你在Done页取消待办项勾选时:- 触发勾选逻辑,先执行AsyncStorage异步写入操作
- 写入完成前回调里的逻辑已经触发Redux状态更新,Done页面的FlatList会立刻过滤掉done=false的条目,对应的
SingleTodo组件直接被卸载 - 等AsyncStorage写入完成执行回调时,对应的
SingleTodo组件已经卸载,此时调用setDone就会触发「无法在已卸载组件上更新状态」的警告。
- 警告仅首次触发的原因:Tab页首次加载完成后会常驻内存,后续列表项的卸载重绘时序不会再踩中React的警告检测阈值,所以后续操作看起来正常,但状态冗余的潜在问题始终存在。
修复方案
核心修复思路是删除SingleTodo里冗余的本地done状态,待办项的完成状态完全以Redux中存储的全局状态为准,不需要在单个列表项组件里额外存一份副本,从根源避免异步回调触发已卸载组件的状态更新。
修改后的SingleTodo组件代码如下:
const SingleTodo = ({title, subtitle, done, todoId}: Props) => { const dispatch = useAppDispatch(); const {todos}: TodosType = useAppSelector(state => state.todoReducer); const checkBoxHandler = async (val: boolean) => { const todoList: TodoInterface[] = [...todos]; const index = todos.findIndex(todo => todo.todoId === todoId); todoList[index].done = val; // 直接更新全局状态,不需要维护本地done状态 dispatch(setAllTodo(todoList)); await AsyncStorage.setItem('todos', JSON.stringify(todoList)); }; const deleteHandler = async () => { const todoList: TodoInterface[] = [...todos]; const index = todos.findIndex(todo => todo.todoId === todoId); todoList.splice(index, 1); dispatch(setAllTodo(todoList)); await AsyncStorage.setItem('todos', JSON.stringify(todoList)); }; return ( <View style={styles.body}> <CheckBox value={done} {/* 直接使用props传入的全局状态值 */} onValueChange={val => checkBoxHandler(val)} style={styles.checkbox} /> <View> <Text style={[styles.title, GlobalStyle.IndieFont]}>{title}</Text> <Text style={[styles.subtitle, GlobalStyle.IndieFont]}>{subtitle}</Text> </View> <View style={styles.trashWrp}> <TouchableOpacity onPress={deleteHandler}> <FontAwesome5Icon style={styles.trashIcon} name="trash" color="#e74c3c" size={20} /> </TouchableOpacity> </View> </View> ); }; export default SingleTodo;
额外加固(可选)
如果后续组件里还有其他异步逻辑,可以加一个挂载标记,在组件卸载时阻止后续的状态更新,写法如下:
const SingleTodo = ({title, subtitle, done, todoId}: Props) => { const isMountedRef = useRef(true); const dispatch = useAppDispatch(); const {todos}: TodosType = useAppSelector(state => state.todoReducer); useEffect(() => { return () => { // 组件卸载时修改标记 isMountedRef.current = false; } }, []); const checkBoxHandler = async (val: boolean) => { const todoList: TodoInterface[] = [...todos]; const index = todos.findIndex(todo => todo.todoId === todoId); todoList[index].done = val; await AsyncStorage.setItem('todos', JSON.stringify(todoList)); // 只有组件仍在挂载状态时才更新全局状态 if (isMountedRef.current) { dispatch(setAllTodo(todoList)); } }; // 其余删除等逻辑可按相同规则加判断 }
改完之后警告会完全消失,同时也解决了本地状态和全局状态不同步的潜在bug。
内容的提问来源于stack exchange,提问作者Mahtab Hossain
相关产品推荐
相关产品推荐

