You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 22:03:29