React Redux TypeError:不可扩展对象无法添加属性问题排查
场景与代码
开发链接收藏网站,使用React-Redux管理状态,需求是将全量分类数据存于外部文件(后续对接后端),通过getCategories reducer根据集合ID筛选分类;添加新分类时同时更新Redux状态和外部Categories数组。
Category Slice代码
import { createSlice, current } from '@reduxjs/toolkit'; import { Categories, Collection } from '../TestData'; import {RandomId} from '../util/RandomId' let categoryList = Categories const categorySlice = createSlice({ name: 'category', initialState: { value: categoryList, currentCollectionId: Collection[0].collectionId }, reducers: { // Retrieve categories corresponding to collectionId getCategories: (state, { payload }) => { const collectionId = payload const latestCatList = [] // Find the categories corresponding with the collectionId categoryList.map((item) => { if (item.collectionId === collectionId) { latestCatList.push(item) } }) //Update the category list the categories state.value = [...latestCatList] //Update the current collectionId state state.currentCollectionId = collectionId return state }, // Add new category addCategory: (state, { payload }) => { const newCategory = { collectionId: state.currentCollectionId, catId: RandomId(), name: payload.name, listEntries: payload.listEntries } state.value.push(newCategory) // Add new category object to state state.value = [...state.value] // NEED TO ADD CATEOGRY TO TEST DATA BUT FOR SOME GODDAMN REASON ITS NOT, maybe push to database straight instead categoryList.push(newCategory) }, } })
外部Categories数组代码
export const Categories = [ { collectionId: '1232', // Reference the collection the categories are under catId: '1232019283', name: 'FIT 2099', // Name of category listEntries: [ // Array of objects for each list entry { name: 'Assignment 1', link: 'Ed forums FIT 2099_ A1' }, { name: 'Assignment 2', link: 'Ed forums FIT 2099_ A2' }, ] }, { collectionId: '1232', catId: '1232120398', name: 'FIT 2004', listEntries: [ { name: 'Tutorial 2', link: 'Master theore,' } ] }, { collectionId: '1231', catId: '123109101', name: 'Movie List', listEntries: [ { name: 'Harry potter 1', link: 'Hurawatch HP 1' } ] }, ]
错误信息
触发addCategory动作时,执行categoryList.push(newCategory)报错:
categorySlice.js:36 Uncaught TypeError: Cannot add property 3, object is not extensible
at Array.push ()
at addCategory (categorySlice.js:36:1)
at createReducer.ts:294:1
at produce (immerClass.ts:94:1)
at createReducer.ts:293:1
at Array.reduce ()
at reducer (createReducer.ts:260:1)
at reducer (createSlice.ts:372:1)
at combination (redux.js:560:1)
at E (:1:33326)
排查与解决方法
原因分析
ES模块中导出的const数组,在现代打包工具(如Webpack、Vite)的开发/生产模式下,会被自动冻结(Object.freeze),目的是防止意外修改模块级常量。你直接将Categories赋值给categoryList,本质上还是指向同一个被冻结的数组,因此push操作会触发"不可扩展"错误。
具体解决步骤
创建可修改的数组副本
初始化时不要直接引用模块导出的数组,通过扩展运算符创建新的可扩展副本:// 替换原来的 let categoryList = Categories let categoryList = [...Categories];优化分类筛选逻辑
用filter替代map+push的低效写法,直接基于本地副本筛选:getCategories: (state, { payload }) => { const collectionId = payload; // 直接用filter筛选对应集合的分类 state.value = categoryList.filter(item => item.collectionId === collectionId); state.currentCollectionId = collectionId; }简化Redux状态更新
Redux Toolkit内置的Immer允许直接修改state,无需手动做不可变更新,同时更新本地副本:addCategory: (state, { payload }) => { const newCategory = { collectionId: state.currentCollectionId, catId: RandomId(), name: payload.name, listEntries: payload.listEntries }; // Immer会自动处理state的不可变更新 state.value.push(newCategory); // 更新本地可修改的副本 categoryList.push(newCategory); }后续对接后端的优化建议
当前本地副本方案仅作为过渡,正式对接后端后,建议将全量分类数据存储在Redux state中,通过createAsyncThunk发起异步请求拉取数据,所有增删改查操作都基于Redux state,不再依赖外部模块的数组变量,彻底避免模块冻结问题。
内容的提问来源于stack exchange,提问作者Matthew Ong

