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

React Native Redux useSelector dispatch成功后数据不刷新

问题原因

页面不刷新是两个典型的Redux使用错误导致的:

  1. Reducer违反不可变更新原则
    Redux的useSelector默认通过引用相等判断状态是否变更,只有当selector返回的引用和上一次不一样时,才会触发组件重渲染。你的Reducer代码里直接取出原state里的dailyData/weeklyMenu/montlyData对象,直接在原对象上做属性赋值、push、splice操作,最后返回时虽然展开了外层state,但这几个嵌套对象还是原来的引用地址,Redux会判定状态没有变化,因此后续dispatch不会触发组件更新。
  2. 组件多余做了一层本地状态拷贝
    你把useSelector取到的值又存到了组件本地useState里,靠useEffect同步,这层中间拷贝不仅多余,还容易出现同步时序问题,额外增加状态不一致的风险。
修复方案

1. 修正Reducer,所有状态更新做不可变拷贝

所有要修改的嵌套对象,必须先创建新的拷贝对象,再在拷贝上做修改,绝对不能直接修改原state上的对象属性。修正后的Reducer代码如下:

import AsyncStorage from '@react-native-async-storage/async-storage';

// 补全和你原有store一致的初始值即可
const initialState = {
  userData: {},
  dailyData: {time: '', protein: 0, fat: 0, carbohydrate: 0, calories: 0},
  weeklyMenu: {week: [], foodId: []},
  montlyData: {days: [], data: []}
}

export default function (state = initialState, action) {
  switch (action.type) {
    case 'USER_INFO': {
      const userData = action.payload.data;
      try {
        const jsonValue = JSON.stringify(userData);
        AsyncStorage.setItem('user_info', jsonValue);
      } catch (err) {
        console.log('user set err', err);
      }
      return {...state, userData};
    }

    case 'ADD_FOOD': {
      const {food, year, month, date, day, meal} = action.payload;
      const time = `${day}/${date}/${month}/${year}`;
      // 所有要修改的对象先做浅拷贝,不直接修改原state
      let newDailyData = {...state.dailyData};
      let newWeeklyMenu = {
        ...state.weeklyMenu,
        week: state.weeklyMenu.week.map(dayMeals => [...dayMeals]),
        foodId: state.weeklyMenu.foodId.map(dayIds => [...dayIds])
      };
      let newMontlyData = {
        ...state.montlyData,
        days: [...state.montlyData.days],
        data: [...state.montlyData.data]
      };

      const dayControl = newDailyData.time.split('/');
      if (time !== newDailyData.time) {
        newDailyData = {protein: 0, fat: 0, carbohydrate: 0, calories: 0, time: ''};
        newMontlyData.days.push(time);
        if (newMontlyData.days.length > 31) {
          newMontlyData.days.shift();
          newMontlyData.data.shift();
        }
        if (`${day}` === dayControl[0] && `(${date}` !== dayControl[1]) {
          newWeeklyMenu.week[day] = [[], [], [], []];
          newWeeklyMenu.foodId[day] = [[], [], [], []];
        }
      }

      newDailyData.time = time;
      newDailyData.protein += Math.round(100 * food.full_nutrients[0].value) / 100;
      newDailyData.fat += Math.round(100 * food.full_nutrients[1].value) / 100;
      newDailyData.carbohydrate += Math.round(100 * food.full_nutrients[2].value) / 100;
      newDailyData.calories += Math.round(100 * food.full_nutrients[3].value) / 100;

      const monthData = [
        newDailyData.protein,
        newDailyData.fat,
        newDailyData.carbohydrate,
        newDailyData.calories,
      ];

      newWeeklyMenu.week[day][meal] = [...newWeeklyMenu.week[day][meal], food.food_name];
      newWeeklyMenu.foodId[day][meal] = [...newWeeklyMenu.foodId[day][meal], food.nix_item_id];

      const montlyIndexDay = newMontlyData.days.indexOf(time);
      newMontlyData.data[montlyIndexDay] = monthData;

      AsyncStorage.setItem('weeklyMenu', JSON.stringify(newWeeklyMenu));
      AsyncStorage.setItem('dailyData', JSON.stringify(newDailyData));
      AsyncStorage.setItem('montly', JSON.stringify(newMontlyData));
      
      console.log('!update data:\n', newWeeklyMenu.week[day][meal]);
      // 返回的更新字段都是新引用,Redux可正确检测变化
      return {...state, weeklyMenu: newWeeklyMenu, dailyData: newDailyData, montlyData: newMontlyData};
    }

    case 'REMOVE_FOOD': {
      const {food, year, month, date, day, meal} = action.payload;
      const time = `${month}/${date}/${year}`;
      // 嵌套对象统一做拷贝后再修改
      let newDailyData = {...state.dailyData};
      let newWeeklyMenu = {
        ...state.weeklyMenu,
        week: state.weeklyMenu.week.map(dayMeals => [...dayMeals]),
        foodId: state.weeklyMenu.foodId.map(dayIds => [...dayIds])
      };
      let newMontlyData = {
        ...state.montlyData,
        days: [...state.montlyData.days],
        data: [...state.montlyData.data]
      };

      const indexFood = newWeeklyMenu.week[day][meal].indexOf(food[0].food_name);
      if (indexFood > -1) {
        newWeeklyMenu.week[day][meal].splice(indexFood, 1);
        newWeeklyMenu.foodId[day][meal].splice(indexFood, 1);
      }

      newDailyData.protein -= food[0].nf_protein / 4;
      newDailyData.fat -= food[0].nf_total_fat / 4;
      newDailyData.carbohydrate -= food[0].nf_total_carbohydrate / 4;
      newDailyData.calories -= food[0].nf_calories / 4;

      const monthData = [
        newDailyData.protein,
        newDailyData.fat,
        newDailyData.carbohydrate,
        newDailyData.calories,
      ];

      const montlyIndexDay = newMontlyData.days.indexOf(time);
      newMontlyData.data[montlyIndexDay] = monthData;

      AsyncStorage.setItem('weeklyMenu', JSON.stringify(newWeeklyMenu));
      AsyncStorage.setItem('dailyData', JSON.stringify(newDailyData));
      AsyncStorage.setItem('montly', JSON.stringify(newMontlyData));
      
      console.log('!update data:\n', newWeeklyMenu.week[day][meal]);
      return {...state, weeklyMenu: newWeeklyMenu, dailyData: newDailyData, montlyData: newMontlyData};
    }

    default:
      return state;
  }
}

2. 简化组件代码,删除多余的本地状态拷贝

不需要把selector的值再存到本地state,直接使用useSelector返回的值即可,避免中间同步层的问题。修正后的Home组件代码如下:

import React from 'react';
import {ScrollView, View, Text} from 'react-native';
import {useSelector} from 'react-redux';

import PieCard from '../components/PieCard/PieCard';
import DailyInfo from '../components/DailyCard/DailyInfoCard';
import CustomHeader from '../components/CustomHeader/CustomHeader';

const Home = ({navigation}) => {
  // 直接使用selector返回值,无需额外存本地state
  const userInfo = useSelector(state => state.userData);
  const dailyData = useSelector(state => state.dailyData);

  const dayControl = dailyData.time?.split('/') || [];
  const days = [
    'Sunday',
    'Monday',
    'Tuesday',
    'Wednesday',
    'Thursday',
    'Friday',
    'Saturday',
  ];
 
  // 饼图数据直接在渲染时计算
  const setPieData = (remaining, consumed) => {
    return {
      dataRate: [
        {
          x: consumed > remaining ? ' ' : `${Math.round(100 * (remaining - consumed)) / 100} g`,
          y: consumed > remaining ? 0 : remaining - consumed,
        },
        {x: `${consumed} g`, y: consumed},
      ],
      color: ['#ff0000', '#80ce48'],
    };
  };

  return (
    <ScrollView style={{flex: 1, backgroundColor: '#eeeeee'}}>
      <CustomHeader title={days[new Date().getDay()]} navigation={navigation} />
      <View style={{flex: 1, marginHorizontal: 10}}>
        {userInfo.daily_calori > 0 ? (
          <DailyInfo
            calories={Math.round(100 * (userInfo.daily_calori / 4)) / 100}
            fat={Math.round(100 * (userInfo.daily_fat / 4)) / 100}
            protein={Math.round(100 * (userInfo.daily_protein / 4)) / 100}
            carbohydrate={Math.round(100 * (userInfo.daily_carbohydrate / 4)) / 100}
          />
        ) : (
          <Text style={{flex: 1, alignItems: 'center'}}>
            You must enter your information on the profile page.
          </Text>
        )}
        {dailyData.protein > 0 && dayControl[0] === `${new Date().getDay()}` ? (
          <View>
            <PieCard
              data={setPieData(userInfo.daily_calori / 4, dailyData.calories)}
              header="Calories"
              title1="remaining"
              title2="consumed"
            />
            <PieCard
              data={setPieData(userInfo.daily_fat / 4, dailyData.fat)}
              header="   Fat"
              title1="remaining"
              title2="consumed"
            />
            <PieCard
              data={setPieData(userInfo.daily_protein / 4, dailyData.protein)}
              header="Protein"
              title1="remaining"
              title2="consumed"
            />
            <PieCard
              data={setPieData(userInfo.daily_carbohydrate / 4, dailyData.carbohydrate)}
              header="Carbohydrate"
              title1="remaining"
              title2="consumed"
            />
          </View>
        ) : null}
      </View>
    </ScrollView>
  );
};
export default Home;

补充说明

如果后续有更多复杂嵌套状态更新,建议直接使用Redux Toolkit,它内置Immer,支持直接写修改对象的语法,会自动生成不可变更新的代码,从根源上避免这类引用不更新的问题。

页面效果截图

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 06:15:35