React Native Redux状态变更后组件未触发重渲染问题
Redux状态更新后React Native UI不自动同步重渲染
问题描述
我在React Native中开发购物篮组件,购物篮内容和总价存储在全局Redux store中。用户选择商品时会派发action,将商品加入购物篮并更新总价。
但全局状态变更后,UI没有同步更新,只有修改本地状态强制组件重渲染时,才会正确显示store里的最新值——Reducer里打印basketPrice每次都能拿到正确的更新结果。
问题相关代码
Reducer代码
const INITIAL_STATE = { basket: [], basketPrice: 0, }; const mainReducer = (state = INITIAL_STATE, action) => { const stateCpy = state; switch (action.type) { case 'SET_BASKET': stateCpy.basket = action.payload return stateCpy; case 'ADD_TO_BASKET': stateCpy.basket.push(action.payload) return stateCpy case 'REMOVE_FROM_BASKET': let tempItems = stateCpy.basket for (var x = 0; x < stateCpy.basket.length; x++) { if (stateCpy.basket[x]._id === action.payload) { tempItems.splice(x, 1) break; } } stateCpy.basket = tempItems return stateCpy case 'SET_BASKET_PRICE': stateCpy.basketPrice = action.payload console.log(stateCpy.basketPrice) return stateCpy default: return state } }; export default mainReducer
Action代码
const setBasket = basket => ({ type: 'SET_BASKET', payload: basket, }); const addToBasket = item => ({ type: 'ADD_TO_BASKET', payload: item, }); const removeFromBasket = item_id => ({ type: 'REMOVE_FROM_BASKET', payload: item_id, }); const setBasketPrice = price => ({ type: 'SET_BASKET_PRICE', payload: price, }); export default actions = { setBasket, addToBasket, removeFromBasket, setBasketPrice }
UI组件代码
... import { useSelector, useDispatch } from 'react-redux' export const RestaurantView = ({ navigation, route }) => { const basket = useSelector((state) => state.basket) const basketPrice = useSelector((state) => state.basketPrice) const dispatch = useDispatch() ... function calcBasketPrice(){ let tempBasketPrice = 0 basket.forEach(element => { tempBasketPrice += element.price }); return tempBasketPrice } function addToBasket(item) { dispatch(actions.setBasketPrice(calcBasketPrice() + item.price)) dispatch(actions.addToBasket(item)) } return ( <View> <ItemCard onPress={addToBasket}> </ItemCard> <Text style={{ textAlign: "right", padding: 15, fontSize: 20 }}> {basketPrice}</Text> </View>) }
问题原因
核心问题是违反了Redux的不可变更新原则。
Redux判断状态是否更新、是否通知订阅组件重渲染的依据是:reducer返回的状态对象引用是否发生变化。你的代码所有更新操作都是直接修改原state对象,没有生成新的状态引用:
const stateCpy = state仅拷贝了原state的引用,没有创建真正的状态副本,后续对stateCpy的修改本质都是直接修改原state- 对
basket数组使用push、splice方法都是直接修改原数组,后续重新赋值也只是传递同一个数组的引用,没有生成新数组 - 直接修改
stateCpy.basketPrice属于修改原对象属性,最终返回的stateCpy和传入的原state是同一个引用,Redux检测到引用无变化,就不会触发UI组件重渲染。这就是reducer内能打印到正确值,但UI不更新的根本原因。
除此之外代码还有两个逻辑缺陷:
- 总价计算放在组件中,分两次dispatch分别更新价格和购物篮内容,存在状态不一致的风险
- 删除商品的逻辑直接修改原数组,同样属于可变更新,会导致后续其他状态更新异常
修复方案
1. 重写Reducer,所有更新都返回新的状态对象,禁止直接修改原state
const INITIAL_STATE = { basket: [], basketPrice: 0, }; const mainReducer = (state = INITIAL_STATE, action) => { switch (action.type) { case 'SET_BASKET': return { ...state, basket: [...action.payload] }; case 'ADD_TO_BASKET': // 生成新数组,总价统一在reducer内计算,不需要单独dispatch const newBasketAdd = [...state.basket, action.payload] return { ...state, basket: newBasketAdd, basketPrice: newBasketAdd.reduce((total, item) => total + item.price, 0) }; case 'REMOVE_FROM_BASKET': // filter返回新数组,不修改原数组 const newBasketRemove = state.basket.filter(item => item._id !== action.payload) return { ...state, basket: newBasketRemove, basketPrice: newBasketRemove.reduce((total, item) => total + item.price, 0) }; case 'SET_BASKET_PRICE': return { ...state, basketPrice: action.payload }; default: return state } }; export default mainReducer
2. 简化组件加购逻辑,无需单独计算价格派发action
function addToBasket(item) { // 仅派发加购动作,reducer统一处理数组更新和价格计算,保证状态一致性 dispatch(actions.addToBasket(item)) }
补充:官方推荐的Redux Toolkit内置了Immer能力,允许直接写修改状态的语法,底层会自动生成不可变更新的新状态,可以从根源上避免这类引用不更新的问题。
内容的提问来源于stack exchange,提问作者J Gi
相关产品推荐
相关产品推荐

