React Native Redux useSelector dispatch成功后数据不刷新
问题原因
页面不刷新是两个典型的Redux使用错误导致的:
- Reducer违反不可变更新原则
Redux的useSelector默认通过引用相等判断状态是否变更,只有当selector返回的引用和上一次不一样时,才会触发组件重渲染。你的Reducer代码里直接取出原state里的dailyData/weeklyMenu/montlyData对象,直接在原对象上做属性赋值、push、splice操作,最后返回时虽然展开了外层state,但这几个嵌套对象还是原来的引用地址,Redux会判定状态没有变化,因此后续dispatch不会触发组件更新。 - 组件多余做了一层本地状态拷贝
你把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
相关产品推荐
相关产品推荐

