Next.js集成Redux报错非draftable值reducer不可返回undefined
问题根因
你遇到的报错和后续状态不更新问题来自两个核心错误:
- 第一个报错
Error: A case reducer on a non-draftable value must not return undefined是reducer写法错误导致的:Redux Toolkit 基于Immer实现状态更新,reducer只有两种合法写法:要么直接修改传入的draft state对象/数组的属性,要么显式return全新的状态值。你最初的写法是给reducer内部的state局部变量重新赋值,既没有修改原始draft,也没有返回新值,最终reducer返回undefined;而布尔值是JS原始类型,本身不可被draft修改,Immer检测到非法返回值直接抛出错误。 - 你把reducer改成return布尔值之后报错消失,但Spinner不显示,是因为你在
useEffect的同一个同步代码块里连续dispatch了开启、关闭两个action,React会自动批量合并同一个事件循环内的多次状态更新,组件只会拿到最终关闭的状态,根本不会捕获到中间spinner为true的渲染帧,自然看不到Spinner出现。另外你在useEffect里打印的spinnerState是闭包捕获的初始值,不会反映dispatch后的最新状态,这个日志没有参考价值。
你之前尝试把state改成对象后报错消失,是因为对象是可draft类型,但你仍然是给局部state变量赋值而不是修改state属性、或者返回新对象,同时没有解决同步连续dispatch被批量合并的问题,所以Spinner依然无法正常展示。
修复方案
1. 修正spinnerSlice的reducer写法
因为spinner状态是布尔原始值,直接返回新状态即可,不需要操作draft:
import { createSlice } from "@reduxjs/toolkit"; const spinnerSlice = createSlice({ name: "spinner", initialState: false, // 初始状态默认关闭加载 reducers: { turnSpinnerOn: () => true, turnSpinnerOff: () => false, setSpinnerState: (_, action) => { return typeof action.payload === "boolean" ? action.payload : false; }, }, }); export const { turnSpinnerOn, turnSpinnerOff, setSpinnerState } = spinnerSlice.actions; export default spinnerSlice.reducer;
2. 修正首页dispatch逻辑
不要在同一个同步执行流里连续触发开启和关闭,要给浏览器留出渲染Spinner的时间,实际业务里一般是在异步请求发起前开Spinner,请求结束后再关闭:
import React, { useEffect } from "react"; import { useSelector, useDispatch } from "react-redux"; import { turnSpinnerOn, turnSpinnerOff } from "../redux/slices/spinnerSlice"; import HomeHero from "../components/PageSections/HomePage/HomeHero/HomeHero"; const Home = () => { const dispatch = useDispatch(); // 如果要调试状态变化,单独加监听即可 // const spinnerState = useSelector((state) => state.spinner); // useEffect(() => { // console.log("最新spinner状态:", spinnerState) // }, [spinnerState]) useEffect(() => { dispatch(turnSpinnerOn()); // 模拟异步数据请求,实际项目替换为你的接口调用 const fetchData = async () => { // 你的数据请求逻辑... await new Promise(resolve => setTimeout(resolve, 800)); dispatch(turnSpinnerOff()); }; fetchData(); }, [dispatch]); return <HomeHero />; }; export default Home;
注:你写的那个循环10000次打印的逻辑是同步阻塞代码,执行期间浏览器完全无法渲染页面,就算状态更新了也看不到Spinner,不要用这种方式模拟耗时加载。
内容的提问来源于stack exchange,提问作者Daniel_Kamel
相关产品推荐
相关产品推荐

