You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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只是对status atom的简单包装,完全没必要存在,直接用status atom即可减少不必要的状态层级。

修复后的代码

主组件代码(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);
  },
});

关键修复点说明

  1. 移除Apps组件内的RecoilRoot,只保留根组件的RecoilRoot包裹,避免状态上下文冲突。
  2. 将Button的onPress={onChangeStatue(hi)}改为onPress={() => onChangeStatue(item)},确保只有点击时才触发状态修改,消除渲染时的无限循环。
  3. 删除冗余的selectStatus selector,直接使用status atom简化状态层级。
  4. 优化onChangeStatue的数组修改逻辑,用map遍历替换slice拼接,避免依赖数组索引(id可能和数组索引不一致)。
  5. 修正Reset功能,直接修改源atom toDos为[],不再尝试修改只读的selector。

内容的提问来源于stack exchange,提问作者Ensnif

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.26 07:32:36