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

Redux Toolkit状态无法更新求助:异步Thunk执行后状态未变更

Redux Toolkit状态无法更新问题

我是Redux Toolkit新手,尽管代码流程看似正确,但状态始终无法更新。以下是相关测试用例、被测代码及测试结果:

测试用例代码

import { ReduxStore } from '../../../react-view/main'
import { configureStoreWith } from '../../../app/store'
import { Dependencies } from '../../../app/dependencies'
import { InMemoryIdProvider } from '../../../infrastructure/idProvider/InMemoryIdProvider'
import { InMemoryRandomDiceProvider } from '../../../infrastructure/randomDiceProvider/InMemoryRandomDiceProvider'
import { rollDice } from '../diceSlice'
import { Die } from '../entities/Die'
import { IdProvider } from '../ports/IdProvider'
import { RandomDiceProvider } from '../ports/randomDiceProvider'

function dieDataBuilder() {
  return new Die('uuid', {
    isHeld: false,
    value: 0,
  })
}

describe('Generate Random Dice', () => {
  let store: ReduxStore
  let dependencies: Dependencies
  let idProvider: IdProvider
  let randomDiceProvider: RandomDiceProvider

  beforeEach(() => {
    idProvider = new InMemoryIdProvider()
    randomDiceProvider = new InMemoryRandomDiceProvider()
    dependencies = {
      idProvider: idProvider,
      randomDiceProvider: randomDiceProvider,
    }
    store = configureStoreWith(dependencies)
  })

  it('should generate 10 random dice', () => {
    const expectedDice = Array(10).fill(dieDataBuilder())

    store.dispatch(rollDice())
    const generatedDice = store.getState().dice.dice

    expect(generatedDice).toStrictEqual(expectedDice)
  })
})

被测系统(SUT)代码

import { createAsyncThunk, createSlice } from '@reduxjs/toolkit'
import { Dependencies } from '../../app/dependencies'
import { Die } from './entities/Die'
import { GenerateRandomDiceUseCase } from './usecases/GenerateRandomDiceUseCase'

type ExtraDependencies = {
  extra: Dependencies
}

export const rollDice = createAsyncThunk<Die[], void, ExtraDependencies>(
  `dice/rollDice`,
  async (thunkAPI, { extra: { randomDiceProvider, idProvider } }) => {
    const dice = new GenerateRandomDiceUseCase(
      randomDiceProvider,
      idProvider,
    ).execute()
    return Promise.resolve(dice)
  },
)

export const initialState = {
  dice: [] as Die[],
  loading: false,
  error: null,
}

export const diceSlice = createSlice({
  name: 'dice',
  initialState,
  reducers: {},
  extraReducers: (builder) => {
    builder.addCase(rollDice.fulfilled, (state, action) => {
      console.log(action.payload)
      state.dice.concat(action.payload)
      return state
    })
  },
})

控制台输出(action.payload内容)

[
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } },
  Die { id: 'uuid', props: { value: 0, isHeld: false } }
]

测试失败结果

Generate Random Dice
  ✕ should generate 10 random dice (109 ms)

● Generate Random Dice › should generate 10 random dice

  expect(received).toStrictEqual(expected) // deep equality

  - Expected  - 72
  + Received  +  1

  - Array [
  -   Die {
  -     "id": "uuid",
  -     "props": Object {
  -       "isHeld": false,
  -       "value": 0,
  -     },
  -   },

[...],

  -   Die {
  -     "id": "uuid",
  -     "props": Object {
  -       "isHeld": false,
  -       "value": 0,
  -     },
  -   },
  - ]
  + Array []

    38 |     const generatedDice = store.getState().dice.dice
    39 |
  > 40 |     expect(generatedDice).toStrictEqual(expectedDice)
       |                           ^
    41 |   })
    42 | })
    43 |

  at Object.<anonymous> (src/core/dice/usecases/GenerateRandomDice.spec.ts:40:27)

Test Suites: 1 failed, 1 total
Tests:       1 failed, 1 total
Snapshots:   0 total
Time:        3.435 s, estimated 4 s
Ran all test suites matching /src\/core\/dice\/usecases\/GenerateRandomDice.spec.ts/i.
error Command failed with exit code 1.

问题原因及修复方案

1. Reducer中数组更新错误

concat方法会返回一个新数组,不会修改原数组state.dice,且手动返回state导致Immer无法追踪到状态变更。在Redux Toolkit的Immer环境中,可直接使用可变方法修改数组:

builder.addCase(rollDice.fulfilled, (state, action) => {
  console.log(action.payload)
  // 使用push批量添加元素,直接修改原数组
  state.dice.push(...action.payload)
  // 无需return state,Immer会自动处理状态变更
})

或直接替换整个数组:

builder.addCase(rollDice.fulfilled, (state, action) => {
  console.log(action.payload)
  state.dice = action.payload
})

2. 测试未等待异步Thunk完成

rollDice是异步Thunk,store.dispatch(rollDice())会返回Promise。测试中直接调用后立即获取状态时,Thunk尚未执行完成,状态未更新。需修改测试为异步函数并await dispatch:

it('should generate 10 random dice', async () => {
  const expectedDice = Array(10).fill(dieDataBuilder())

  // 等待Thunk执行完成
  await store.dispatch(rollDice())
  const generatedDice = store.getState().dice.dice

  expect(generatedDice).toStrictEqual(expectedDice)
})

修复以上两个问题后,测试即可正常通过,状态也会正确更新。


内容的提问来源于stack exchange,提问作者A Mehmeto

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.04 22:35:27