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

EntityAdapter的updateMany无法更新状态,addMany却正常,原因何在?

问题:Redux Toolkit EntityAdapter的updateMany无法更新状态,addMany正常工作

问题背景

我开发了一款应用,用户点击城市按钮传递坐标,系统根据该坐标向服务器请求最近的可用场所,格式化后存入Redux状态。目前遇到的问题是:EntityAdapter的updateMany方法无法更新状态,但addMany能正常工作。

相关代码

App.tsx

import React, { useEffect, useState } from "react";
import "./App.css";
import { useAppSelector } from "./hook";
import { useRequestPlaces } from "./hooks/index";
import { useAppDispatch } from "./hook";

const cities = [
  { name: "New York", latlong: "40.760898,-73.961219" },
  { name: "London", latlong: "51.522479,-0.104528" },
  { name: "London Suburb", latlong: "51.353340,-0.032366" },
  { name: "Desert", latlong: "22.941602,25.529665" },
];
const defaultLatlong = "40.760898,-73.961219";

function App() {
  const dispatch = useAppDispatch();
  const fetchPlaces = useRequestPlaces();
  const { ids, entities } = useAppSelector((state) => state.places);
  const [latlong, setLatlong] = useState(defaultLatlong);

  const minRadius = 50;

  useEffect(() => {
    fetchPlaces(minRadius, latlong, dispatch);
    console.log(entities);
  }, [fetchPlaces, latlong, entities, ids]);

  return (
    <div className="App">
      <header className="App-header">
        {cities.map((city) => {
          return (
            <button
              type="button"
              className="btn btn-outline-light"
              onClick={() => {
                setLatlong(city.latlong);
                console.log(latlong);
              }}
            >
              {city.name}
            </button>
          );
        })}
      </header>
      <main>
        {ids.map((id, index) => {
          const place = entities[id];
          return (
            <div
              className="card mx-auto mt-2"
              key={index}
              style={{ width: "18rem" }}
            >
              <div className="card-body">
                <h5 className="card-title">{place?.name}</h5>
                <h6 className="card-subtitle mb-2 text-muted">
                  <ul>
                    {place?.categories.map((category) => {
                      return <li key={category.id}>{category.name}</li>;
                    })}
                  </ul>
                </h6>
                <p className="card-text">
                  Distance: {place?.distance} meters
                  <br />
                  Adress: {place?.location}
                </p>
              </div>
            </div>
          );
        })}
      </main>
    </div>
  );
}

export default App;

fetch.ts

import { Dispatch } from "react";
import { getClosestPlaces } from "./getClosestPlaces";
import { placesActions } from "../../slices";
import { Action } from "redux";
import client from "./client";

const fetch = async (
  radius: number,
  latlong: string,
  dispatch: Dispatch<Action>
) => {
  const { fetchPlaces } = client();
  const params = {
    client_id: `${process.env.REACT_APP_CLIENT_ID}`,
    client_secret: `${process.env.REACT_APP_CLIENT_SECRET}`,
    ll: latlong,
    radius: radius.toString(),
    limit: "50",
  };
  const response = await fetchPlaces(new URLSearchParams(params).toString());
  const { results } = response.data;
  if (results.length !== 0) {
    const closestPlaces = getClosestPlaces(results);

// 此处所有所需数据正常,是对象数组,我将其传入addPlaces动作

    dispatch(placesActions.addPlaces(closestPlaces));
  } else if (results.length === 0 && radius < 1600) {
    fetch(radius + 50, latlong, dispatch);
  }
  return [];
};

export { fetch };

placesSlice.ts

import {
  createSlice,
  EntityState,
  createEntityAdapter,
} from "@reduxjs/toolkit";
import { FormattedPlace } from "./index";
import { RootState } from "./index";
import { Slice } from "@reduxjs/toolkit/src/createSlice";
import { SliceActions } from "@reduxjs/toolkit/dist/query/core/buildSlice";

const placesAdapter = createEntityAdapter<FormattedPlace>();
const initialState = placesAdapter.getInitialState();

type PlacesReducerActions = {
  addPlaces(state: any, { payload }: { payload: any }): void;
};

export type PlacesSliceType = Slice<
  EntityState<FormattedPlace>,
  PlacesReducerActions,
  "places"
>;

const placesSlice: PlacesSliceType = createSlice({
  name: "places",
  initialState,
  reducers: {
    addPlaces(state, { payload }) {

// 此处调用updateMany
      placesAdapter.updateMany(state, payload);
    },
  },
});
export const selectors = placesAdapter.getSelectors<RootState>(
  (state) => state.places
);
export const { actions } = placesSlice;
export default placesSlice.reducer;

核心原因

updateMany和addMany的参数格式要求完全不同:

  • addMany接受实体对象数组(每个对象包含id和其他业务字段)
  • updateMany接受更新操作对象数组,每个对象必须包含id和changes字段,格式为{ id: string/number, changes: Partial<FormattedPlace> }

你的代码中直接把closestPlaces(实体数组)传给了updateMany,不符合它的参数要求,因此无法生效。而addMany可以直接接收实体数组,所以能正常工作。

另外从业务逻辑看,切换城市时应该是替换/新增场所数据,而非更新已有数据,这种场景下addMany或setMany(清空原有数据再添加)才是正确选择。

解决方案

方案1:使用setMany替换全部数据(推荐)

适合切换城市的场景,先清空现有状态再添加新数据:

// placesSlice.ts中修改addPlaces reducer
addPlaces(state, { payload }) {
  placesAdapter.setMany(state, payload);
}

方案2:转换参数格式适配updateMany

如果确实需要更新已有实体,把closestPlaces转换成updateMany要求的格式:

// fetch.ts中修改dispatch部分
const updatePayload = closestPlaces.map(place => ({
  id: place.id,
  changes: place
}));
dispatch(placesActions.addPlaces(updatePayload));

方案3:优化业务逻辑,避免新旧数据混杂

切换城市时先清空之前的场所数据:

// placesSlice中新增清空reducer
reducers: {
  addPlaces(state, { payload }) {
    placesAdapter.setMany(state, payload);
  },
  clearPlaces(state) {
    placesAdapter.removeAll(state);
  }
}

// App.tsx的useEffect中先清空再请求
useEffect(() => {
  dispatch(placesActions.clearPlaces());
  fetchPlaces(minRadius, latlong, dispatch);
}, [fetchPlaces, latlong, dispatch]);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:10:27