Redux Slice报错:Cannot add property bookId, object is not extensible及架构咨询
Redux Toolkit多资源管理问题:对象不可扩展错误修复与统一搜索方案
一、对象不可扩展错误修复
报错Cannot add property bookId, object is not extensible的核心原因是:从json-server返回的对象被Redux Toolkit默认冻结(不可扩展),你直接在selectId里给原对象添加属性,违反了不可扩展对象的规则,而且selectId本身应该是纯函数,只负责返回标识,不能修改输入实体。
修复方法有两种:
- 方法1:预处理数据,生成新对象再传入adapter
在获取数据的thunk里,不要直接修改原返回对象,用扩展运算符创建新对象并添加自定义ID:// toySlice.ts里的fetchToys thunk示例 export const fetchToys = createAsyncThunk('toys/fetchToys', async () => { const res = await fetch('/toys'); const rawToys = await res.json(); // 生成带toyId的新对象数组 return rawToys.map(toy => ({ ...toy, toyId: uuid() })); }); // 在slice的extraReducers里直接用adapter.setAll extraReducers: (builder) => { builder.addCase(fetchToys.fulfilled, (state, action) => { toysAdapter.setAll(state, action.payload); }); } - 方法2:修改selectId,不修改原对象
如果原数据本身有唯一ID,直接用原ID加类型标识作为唯一键,不用新增属性:
要是原数据没有ID,还是用方法1预处理生成带ID的新对象。const toysAdapter = createEntityAdapter<Toy>({ selectId: (toy) => `toy-${toy.id}` // 用原id加前缀区分和book的id });
二、类型选择:单一Post类型还是保留Toy/Book?
优先保留Toy和Book两个独立类型,理由:
- 两类资源的业务属性大概率不同(比如玩具可能有适用年龄、类别,书籍有作者、ISBN),分开类型能让代码逻辑更清晰,后续扩展各自功能时不会互相干扰。
- 如果强行合并成
Post类型,需要用联合类型type Post = Toy | Book,后续操作时要频繁做类型判断,反而增加复杂度,不如分开后再做统一搜索的聚合。
三、多资源统一搜索的最优实现方式
根据你的场景推荐三种方案,按实现复杂度和性能排序:
1. 组件层直接聚合过滤(适合小数据量场景)
用useSelector分别拿到两类资源,合并后根据搜索关键词过滤,实现最简单:
import { useSelector, useDispatch } from 'react-redux'; import { selectAllToys } from './toySlice'; import { selectAllBooks } from './bookSlice'; import { useState } from 'react'; function SearchBar() { const [searchTerm, setSearchTerm] = useState(''); const toys = useSelector(selectAllToys); const books = useSelector(selectAllBooks); // 合并并过滤结果 const filteredItems = [...toys, ...books].filter(item => { // 根据不同类型取对应的搜索字段 const searchText = 'name' in item ? item.name : item.title; return searchText.toLowerCase().includes(searchTerm.toLowerCase()); }); return ( <> <input value={searchTerm} onChange={(e) => setSearchTerm(e.target.value)} /> <div>{/* 渲染filteredItems */}</div> </> ); }
2. 用记忆化选择器优化性能(适合频繁搜索场景)
用Redux Toolkit内置的createSelector创建记忆化选择器,只有当toys、books或搜索关键词变化时才重新计算结果,避免不必要的重渲染:
// 新建一个selectors.ts文件 import { createSelector } from '@reduxjs/toolkit'; import { selectAllToys } from './toySlice'; import { selectAllBooks } from './bookSlice'; // 假设你在searchSlice里存了搜索关键词 const selectSearchTerm = (state: RootState) => state.search.searchTerm; // 记忆化的过滤结果选择器 export const selectFilteredResults = createSelector( [selectAllToys, selectAllBooks, selectSearchTerm], (toys, books, searchTerm) => { const lowerTerm = searchTerm.toLowerCase(); return [...toys, ...books].filter(item => { const text = ('name' in item ? item.name : item.title).toLowerCase(); return text.includes(lowerTerm); }); } ); // 在组件里使用 const filteredItems = useSelector(selectFilteredResults);
3. 独立搜索Slice管理逻辑(适合大数据量或需要缓存场景)
创建专门的searchSlice,集中管理搜索状态和逻辑,比如缓存搜索结果、处理加载状态:
// searchSlice.ts import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; import { RootState } from './store'; import { selectAllToys } from './toySlice'; import { selectAllBooks } from './bookSlice'; import { Toy, Book } from './types'; // 异步搜索thunk export const runSearch = createAsyncThunk( 'search/run', async (searchTerm: string, { getState }) => { const state = getState() as RootState; const toys = selectAllToys(state); const books = selectAllBooks(state); return [...toys, ...books].filter(item => { const text = ('name' in item ? item.name : item.title).toLowerCase(); return text.includes(searchTerm.toLowerCase()); }); } ); const searchSlice = createSlice({ name: 'search', initialState: { results: [] as (Toy | Book)[], loading: false, currentTerm: '' }, reducers: { setSearchTerm: (state, action) => { state.currentTerm = action.payload; } }, extraReducers: (builder) => { builder .addCase(runSearch.pending, (state) => { state.loading = true; }) .addCase(runSearch.fulfilled, (state, action) => { state.loading = false; state.results = action.payload; }); } }); export const { setSearchTerm } = searchSlice.actions; export default searchSlice.reducer;
在组件里,输入变化时触发runSearch,直接从searchSlice取结果即可。
内容的提问来源于stack exchange,提问作者Himanshu Sharma
相关产品推荐
相关产品推荐

