Redux中如何仅更新状态对象的部分属性?现有方案是否最优?
嘿,看了你这个用getA和getB两个action更新共享状态部分字段的场景,你的思路方向是没问题的,但有几个细节可以调整,让代码更符合Redux的最佳实践,也更简洁易维护:
1. 别在action里提前拿现有状态
你现在在getA里先通过getState()获取现有状态再传到action里,其实完全没必要哦。Reducer本身就会接收当前的完整状态,所以状态合并的逻辑放在Reducer里才是更合理的——action只需要携带新获取到的数据就好,不用传递现有状态,减少冗余。
举个修改后的action示例:
// Action export function getA() { return async (dispatch) => { dispatch({ type: PENDING }); try { // 模拟API获取数据"A" const dataA = await fetch('/api/data-a').then(res => res.json()); dispatch({ type: GET_A_SUCCESS, payload: dataA }); } catch (err) { dispatch({ type: GET_A_FAILURE, error: err.message }); } }; } // getB同理 export function getB() { return async (dispatch) => { dispatch({ type: PENDING }); try { const dataB = await fetch('/api/data-b').then(res => res.json()); dispatch({ type: GET_B_SUCCESS, payload: dataB }); } catch (err) { dispatch({ type: GET_B_FAILURE, error: err.message }); } }; }
2. 在Reducer里做状态的不可变合并更新
Reducer的核心职责就是接收当前状态和action,返回不可变的新状态。针对部分更新,用ES6的对象展开运算符(...)来浅拷贝现有状态,再覆盖需要更新的字段,这是Redux里最常用的方式,既保证了状态不可变,又清晰易懂:
// Reducer const initialState = { myObj: { a: null, b: null }, isLoading: false, error: null }; function myReducer(state = initialState, action) { switch (action.type) { case PENDING: return { ...state, isLoading: true, error: null }; case GET_A_SUCCESS: return { ...state, isLoading: false, myObj: { ...state.myObj, // 拷贝现有myObj的所有字段 a: action.payload // 只更新a字段 } }; case GET_B_SUCCESS: return { ...state, isLoading: false, myObj: { ...state.myObj, b: action.payload // 只更新b字段 } }; case GET_A_FAILURE: case GET_B_FAILURE: return { ...state, isLoading: false, error: action.error }; default: return state; } }
3. 进阶优化:用工具简化复杂状态更新
如果你的状态结构比较深、嵌套多,手写展开运算符会有点繁琐,这时候可以试试Immer库——它允许你用“看起来可变”的写法来编写更新逻辑,底层自动帮你生成不可变的新状态,代码会清爽很多:
import { produce } from 'immer'; function myReducer(state = initialState, action) { return produce(state, draft => { switch (action.type) { case PENDING: draft.isLoading = true; draft.error = null; break; case GET_A_SUCCESS: draft.isLoading = false; draft.myObj.a = action.payload; break; case GET_B_SUCCESS: draft.isLoading = false; draft.myObj.b = action.payload; break; case GET_A_FAILURE: case GET_B_FAILURE: draft.isLoading = false; draft.error = action.error; break; } }); }
另外,强烈推荐你试试Redux官方推出的Redux Toolkit,它集成了thunk、Immer、切片式reducer等工具,能帮你砍掉大量样板代码,比如用createAsyncThunk处理异步action,createSlice定义reducer和action,开发效率会提升不少。
总的来说,你最初的思路是对的,但把现有状态传到action里是多余的,把状态合并逻辑放在Reducer中,才更符合Redux的设计原则,也让代码更清晰好维护~
内容的提问来源于stack exchange,提问作者EJP

