React Native + Recoil开发TodoList遇无限重渲染错误求助
解决React Native + Recoil TodoList无限重渲染问题
核心问题分析
- 重复包裹RecoilRoot:App和Apps组件都加了RecoilRoot,RecoilRoot只需要在根组件套一次,多层包裹会导致状态上下文冲突,触发异常重渲染。
- 渲染时直接执行状态修改函数:渲染待办项Button时,
onPress={onChangeStatue(hi)}的写法会在每次组件渲染时立刻调用onChangeStatue,修改toDos状态后又触发重渲染,形成无限循环,这是报错的直接原因。 - 错误操作只读Selector:
selectToDo是派生状态的selector(只有get方法),但代码里用useRecoilState获取并尝试直接修改它,不符合Recoil设计——派生状态只能随依赖的源atom变化自动更新,不能手动设置。 - 冗余Selector:
selectStatus只是对statusatom的简单包装,完全没必要存在,直接用statusatom即可减少不必要的状态层级。
修复后的代码
主组件代码(App.jsx)
/** * Sample React Native App * https://github.com/facebook/react-native * * @format * @flow strict-local */ import { Alert } from 'react-native'; import { SafeAreaView, ScrollView, StatusBar, StyleSheet, TextInput, Button, useColorScheme, View, } from 'react-native'; import { Colors } from 'react-native/Libraries/NewAppScreen'; import { RecoilRoot, useRecoilValue, useSetRecoilState, useRecoilState } from 'recoil'; import { status, toDos, selectToDo } from './store/toDo'; import { useState } from 'react'; const Apps = () => { const toDoAtom = useRecoilValue(toDos); const setToDoAtom = useSetRecoilState(toDos); const [nowStatus, setStatus] = useRecoilState(status); const [toDo, onChangeToDo] = useState(''); const selectToDos = useRecoilValue(selectToDo); const [id, setId] = useState(1); const isDarkMode = useColorScheme() === 'dark'; const backgroundStyle = { backgroundColor: isDarkMode ? Colors.darker : Colors.lighter, }; const onSetData = () => { const newToDoList = [ ...toDoAtom, { id: id, contents: toDo, status: "DOING" } ]; setToDoAtom(newToDoList); setId(prevId => prevId + 1); onChangeToDo(""); Alert.alert('submit'); }; const onChangeStatue = (data) => { // 用id匹配替换索引,避免id和数组索引不一致的问题 const updatedLists = toDoAtom.map(item => item.id === data.id ? { ...item, status: "DONE" } : item ); setToDoAtom(updatedLists); }; const onRest = () => { setToDoAtom([]); setId(1); }; const onStatus = () => { setStatus(prev => prev === "DOING" ? "DONE" : "DOING"); }; return ( <SafeAreaView style={backgroundStyle}> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} /> <ScrollView contentInsetAdjustmentBehavior="automatic" style={backgroundStyle}> <View style={{ backgroundColor: isDarkMode ? Colors.black : Colors.white, }}> <View> <TextInput style={styles.input} onChangeText={onChangeToDo} value={toDo} /> <Button title='submit' color="blue" onPress={onSetData} /> <Button title="Reset" color="red" onPress={onRest} /> </View> <Button title={nowStatus} onPress={onStatus} /> {selectToDos?.map((item) => ( <View key={item.id}> {/* 用箭头函数包裹,确保只有点击时才执行状态修改 */} <Button title={item.contents} onPress={() => onChangeStatue(item)} /> </View> ))} </View> </ScrollView> </SafeAreaView> ); }; export const App = () => { return ( <RecoilRoot> <Apps /> </RecoilRoot> ); }; const styles = StyleSheet.create({ input: { height: 40, margin: 12, borderWidth: 1, padding: 10, }, }); export default App;
Recoil状态管理代码(store/toDo.js)
import { selector, atom } from 'recoil'; export const status = atom({ key: 'status', default: 'DOING' }); export const toDos = atom({ key: 'toDos', default: [] }); export const selectToDo = selector({ key: 'selectToDo', get: ({ get }) => { const originalToDos = get(toDos); const nowStatus = get(status); return originalToDos.filter((toDo) => toDo.status === nowStatus); }, });
关键修复点说明
- 移除Apps组件内的RecoilRoot,只保留根组件的RecoilRoot包裹,避免状态上下文冲突。
- 将Button的
onPress={onChangeStatue(hi)}改为onPress={() => onChangeStatue(item)},确保只有点击时才触发状态修改,消除渲染时的无限循环。 - 删除冗余的
selectStatusselector,直接使用statusatom简化状态层级。 - 优化
onChangeStatue的数组修改逻辑,用map遍历替换slice拼接,避免依赖数组索引(id可能和数组索引不一致)。 - 修正Reset功能,直接修改源atom
toDos为[],不再尝试修改只读的selector。
内容的提问来源于stack exchange,提问作者Ensnif
相关产品推荐
相关产品推荐

