Redux中扁平化对象数组的正确方式?现有实现存多类问题
我太懂这种被嵌套状态折磨的感觉了——递归更新reducer写到头大,connect()还因为浅比对瞎触发重渲染,完全是给自己添堵!你之前尝试扁平化但没达到效果,大概率是没找对扁平状态的正确姿势,咱们一步步来解决:
第一步:把数组转成「键值对+顺序数组」的扁平结构
你现在的questions是数组,这才是导致递归更新、状态复杂的根源。咱们把它拆成两部分:
questionsById:以问题id为键的对象,直接存储每个问题的完整数据questionIds:存储所有问题id的数组,用来维护原有的顺序
比如把你给的接口响应转成扁平结构:
// 把接口返回的原始数据转成扁平状态 const flattenQuestionData = (rawData) => { const questionsById = {}; const questionIds = []; rawData.questions.forEach(question => { questionsById[question.id] = { ...question }; // 浅拷贝避免直接修改原数据 questionIds.push(question.id); }); return { questionsById, questionIds }; }; // 初始化store时就用这个扁平结构 const initialState = flattenQuestionData(questionData);
第二步:Reducer更新彻底告别递归
扁平结构后,不管是修改、新增还是删除问题,都能通过id直接定位,再也不用遍历数组或者递归嵌套了:
const questionsReducer = (state = initialState, action) => { switch(action.type) { // 示例:更新某个问题的state属性 case 'UPDATE_QUESTION_STATE': { const { questionId, newState } = action.payload; return { ...state, questionsById: { ...state.questionsById, // 只更新目标问题的属性,其他对象引用不变 [questionId]: { ...state.questionsById[questionId], state: newState } } }; } // 新增问题 case 'ADD_QUESTION': { const newQuestion = action.payload; return { ...state, questionsById: { ...state.questionsById, [newQuestion.id]: newQuestion }, questionIds: [...state.questionIds, newQuestion.id] }; } default: return state; } };
第三步:解决connect()浅比较的痛点
深度嵌套时,哪怕只改一个小属性,父对象的引用也会变化,connect()的浅比对会误以为整个状态都变了,导致组件瞎重渲染。扁平结构后,你可以精准获取组件需要的状态:
// 比如某个只展示单个问题的组件 const mapStateToProps = (state, ownProps) => { return { // 只取当前组件需要的那个问题对象 question: state.questions.questionsById[ownProps.questionId] }; }; export default connect(mapStateToProps)(QuestionComponent);
这样只有当这个特定问题的属性变化时,组件才会重渲染——因为connect()只会对比question对象的引用,而我们更新时只修改了对应id的对象,其他问题的引用完全没变,完美避免无效重渲染。
额外提醒:如果还有深层嵌套子属性?
要是你的问题里还有嵌套的子结构(比如选项、答案),同样用「键值对+顺序数组」的方式扁平化,比如optionsById、optionIdsByQuestionId,坚决不让状态嵌套超过两层。
内容的提问来源于stack exchange,提问作者Huy Nguyễn Quang
相关产品推荐
相关产品推荐

