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
相关产品推荐
相关产品推荐

