Redux中Immutable对象数组更新:当前实现是否存在嵌套对象突变?
问题:嵌套对象的Immutable更新疑问
我拥有如下对象数组结构,希望基于API响应为所有嵌套对象插入新的键值对。请问我下方的解决方案是否会导致嵌套对象突变?若是,有什么更优实现能符合Immutable更新模式?
Reducer初始结构:
[{ recordID: '4B6987A1-4179-426A-B38A-8B122995D0F8:ABPerson', givenName: 'A', middleName: '', familyName: '', phoneNumbers: [{ label: 'mobile', number: '12345679' }], }]
API传入数据结构:
[{ recordID: '4B6987A1-4179-426A-B38A-8B122995D0F8:ABPerson', givenName: 'A', middleName: '', familyName: '', isUser: true, // 新的键值对 phoneNumbers: [{ label: 'mobile', number: '12345679' }], }]
当前更新store的方式:
case GET_NON_USERS_SUCCESS: { const newArr = state.inMemoryContact.map((record, index) => { return { ...record, ...action.payload.getNonUsers[index] }; }); return { ...state, inMemoryContact: newArr }; }
Action定义:
const getNonUsersSuccess = data => ({ type: GET_NON_USERS_SUCCESS, payload: data });
回答
首先直接给结论:你的当前实现不会导致顶层对象的突变,但存在一个潜在的Immutable风险——嵌套的phoneNumbers数组是浅拷贝的,会和原state共享引用。如果后续有代码修改这个嵌套数组里的对象(比如修改某个号码的number值),就会意外修改原state的内容,违反Immutable更新的原则。
为什么会有这个风险?
你用的对象展开{ ...record }是浅拷贝:它只会复制顶层的键值对,对于嵌套的数组或对象,只会把它们的引用复制过去,而不是创建一个全新的独立副本。也就是说,新数组里的phoneNumbers和原state里的phoneNumbers指向的是同一个数组对象。
更优的Immutable实现方案
如果要严格遵循Immutable模式,确保所有层级的对象/数组都不会共享引用,我们需要对嵌套层级也做拷贝:
方案1:手动深拷贝嵌套数组
如果只是新增顶层键值对,同时确保嵌套数组独立,可以修改reducer如下:
case GET_NON_USERS_SUCCESS: { const newArr = state.inMemoryContact.map((record, index) => { const payloadRecord = action.payload.getNonUsers[index]; return { // 拷贝顶层属性 ...record, ...payloadRecord, // 对嵌套的phoneNumbers数组做浅拷贝,生成新数组 phoneNumbers: [...record.phoneNumbers] }; }); return { ...state, inMemoryContact: newArr }; }
如果API返回的phoneNumbers可能有更新(比如新增/修改号码),可以改为合并payload里的数组:
case GET_NON_USERS_SUCCESS: { const newArr = state.inMemoryContact.map((record, index) => { const payloadRecord = action.payload.getNonUsers[index]; return { ...record, ...payloadRecord, // 优先使用payload里的phoneNumbers,否则拷贝原数组 phoneNumbers: payloadRecord.phoneNumbers ? [...payloadRecord.phoneNumbers] : [...record.phoneNumbers] }; }); return { ...state, inMemoryContact: newArr }; }
方案2:使用Immer库简化Immutable操作
如果你的state嵌套层级比较深,或者经常需要处理Immutable更新,推荐使用Immer库。它允许你用类似mutable的写法来编写代码,内部会自动帮你生成Immutable的新state,代码更简洁易读:
import produce from 'immer'; // ... case GET_NON_USERS_SUCCESS: { return produce(state, draft => { state.inMemoryContact.forEach((_, index) => { // 直接合并payload的属性到draft对象,Immer会自动处理Immutable Object.assign(draft.inMemoryContact[index], action.payload.getNonUsers[index]); // 如果需要更新嵌套数组,直接操作即可,比如: // draft.inMemoryContact[index].phoneNumbers = [...action.payload.getNonUsers[index].phoneNumbers]; }); }); }
总结
- 当前写法在只新增顶层键值对的场景下,不会有顶层对象的突变问题,但嵌套数组仍共享引用,存在后续误修改的风险;
- 如果要严格遵循Immutable模式,需要手动拷贝所有嵌套的对象/数组,或者使用Immer这类工具库简化操作。
内容的提问来源于stack exchange,提问作者amo
相关产品推荐
相关产品推荐

