React+Redux Toolkit渲染API数据出现.slice is not a function报错
问题根因排查
.slice() is not a function报错和接口数据拉取异常是多段逻辑问题叠加导致,和Redux Toolkit基础配置无关:
- 初始渲染阶段空值直接调用数组方法:组件首次加载时Redux中
list初始值是空数组,const [mockData, setMockData] = useState(list[0])取到的值为undefined,后续直接对mockData调用.slice()会直接抛出方法不存在的错误。 - useState初始值不会随Redux状态同步:
useState的初始参数仅在组件首次挂载时生效,后续Redux中list更新后,mockData不会自动同步最新值,代码中也没有写同步逻辑,所以始终拿不到接口返回的数据。 - Redux存储数据时多包了一层数组:如果接口本身返回的就是列表数组,
getList.fulfilled中写的state.list = [action.payload]会把整个返回数组包成二维结构,就算数据同步成功,后续分页逻辑也会和预期不符。 - 请求逻辑缺少状态判断和异常捕获:
fetch默认不会因HTTP 4xx/5xx状态码标记为请求失败,直接调用res.json()可能拿到错误响应体而非业务数据;另外Postman不受浏览器同源策略限制,如果接口没配置CORS响应头,浏览器会直接拦截跨域请求,这也是Postman能调通但页面拿不到数据的常见原因。 - 缺少加载、错误状态兜底:代码中没有处理请求中、请求失败、数据为空的场景,直接渲染业务逻辑很容易触发报错。
修复方案
1. 修正Redux Slice逻辑
修正数据存储结构,补全请求状态判断和错误捕获,替换已废弃的extraReducers对象写法:
import { createSlice, createAsyncThunk } from '@reduxjs/toolkit'; export const getList = createAsyncThunk('list/getList', async (_id, { rejectWithValue }) => { try { const res = await fetch('http://13.xx.94.xx/neon/api/v1/List/RetrieveAll'); if (!res.ok) throw new Error(`请求异常,状态码:${res.status}`); const data = await res.json(); // 根据接口实际返回结构调整,直接返回数组则取data,返回{data:数组}结构则取data.data return Array.isArray(data) ? data : data.data; } catch (err) { return rejectWithValue(err.message); } }); const listSlice = createSlice({ name: 'list', initialState: { list: [], loading: false, error: null }, reducers: {}, extraReducers: (builder) => { builder .addCase(getList.pending, (state) => { state.loading = true; state.error = null; }) .addCase(getList.fulfilled, (state, action) => { state.loading = false; // 直接存储返回的数组,不要额外套方括号 state.list = action.payload; }) .addCase(getList.rejected, (state, action) => { state.loading = false; state.error = action.payload; }) } }); export default listSlice.reducer;
2. 修正组件渲染逻辑
移除冗余的mockData本地状态,直接使用Redux存储的列表数据,补全状态兜底和缺失的状态定义:
const List = () => { // 优化useSelector写法,避免每次渲染生成新对象导致不必要重渲染 const { loading, list, error } = useSelector((state) => state.list); const dispatch = useDispatch(); // 补全原代码缺失的分页页码状态 const [pageNum, setPageNum] = useState(0); // 原代码用到的弹窗状态如果是本地状态也需要补全定义 const [isOpen, setIsOpen] = useState(false); useEffect(() => { dispatch(getList()); }, [dispatch]); // 状态兜底渲染 if (loading) return <div>数据加载中...</div>; if (error) return <div>加载失败:{error}</div>; if (!Array.isArray(list) || list.length === 0) return <div>暂无数据</div>; const dataPerPage = 10; const dataPageVisited = pageNum * dataPerPage; // 直接对Redux中存储的列表做分页处理 const displayData = list.slice(dataPageVisited, dataPageVisited + dataPerPage).map((datum) => ( <tr key={datum.id}> <td className="text-sm leading-5 py-4 px-3">{datum.id}</td> <td className="py-4 uppercase text-center">{datum.name}</td> <td className="py-4 pr-4 pl-20"> {datum.status === 'Active' ? ( <span className="flex items-center bg-green-300 py-0.3 px-0.2 w-14 rounded-xl text-white"> <GoPrimitiveDot className="text-white" /> {datum.status} </span> ) : ( <span className="flex items-center bg-red-400 py-0.3 px-0.2 w-16 rounded-xl text-white"> <GoPrimitiveDot className="text-white" /> {datum.status} </span> )} </td> <td className="py-4 pl-4">{datum.websiteUrl}</td> <td className="py-4 pl-10"> <span className="inline-block text-textTeams py-0.5 px-0.4 w-16 bg-indigo-50 rounded-lg text-center hover:cursor-pointer"> {datum.category} </span> </td> <td className="py-4 px-6"> <span className="flex justify-between"> <button type="button"> <FiSearch className="search-icon hover:cursor-pointer w-5 h-5 text-searchColor" /> </button> <RiDeleteBinLine className="delete-icon hover:cursor-pointer w-5 h-5 text-binColor" onClick={() => setIsOpen(true)} /> <button type="button"> <FiEdit2 className="pen-icon hover:cursor-pointer w-5 h-5 text-penColor" /> </button> </span> </td> </tr> )); return( <> {displayData} {/* 补全分页切换组件,修改pageNum即可触发分页切换 */} </> ) }
3. 跨域问题处理
如果改完上述代码控制台仍报CORS相关错误:
- 开发环境可以在Vite/Webpack等构建工具中配置接口代理,转发请求绕过浏览器同源限制
- 生产环境需要后端给接口配置
Access-Control-Allow-Origin等CORS响应头,放开前端域名的访问权限
内容的提问来源于stack exchange,提问作者Tunde
相关产品推荐
相关产品推荐

