Redux - createSlice 内的标准化嵌套数据组织
Posted
技术标签:
【中文标题】Redux - createSlice 内的标准化嵌套数据组织【英文标题】:Redux - Normalized nested data organization inside createSlice 【发布时间】:2021-01-13 16:03:09 【问题描述】:我有一个从我的 API 返回的深度嵌套的数据对象,它看起来像下面的 JSON。
我正在使用 Redux 工具包的 createSlice
创建 trip
的切片
所以目前在我的createSlice
中,我想存储一系列行程。
我还希望能够更新单个行程或部分行程
例如,假设我想更新旅行项目的开始日期 或者,假设我想更新旅行项目的成员姓名我的问题和疑虑:
我目前将所有这些实体都返回到trip
createSlice,但我不确定一旦实体被规范化,它们是否应该被分成单独的createSlice
s?如果是这样,这是如何完成的,或者这是一种反模式?
应如何在initialState
中定义嵌套实体?
是否应该在initalState
中定义所有规范化实体?
如果我这样做,当我想更新 trip_item
或 trip_item_member
时,我的减速器会是什么样子?
我的标准化数据看起来是否“正确”?我省略了在trips_items_members
和trip_members
之间使用mergeStrategy
,我知道我应该这样做,但还没有弄清楚它是如何工作的,或者这里是否有必要?
注意:
RTK 文档 here 中有一个示例,它显示 createSlice
与 3 个单独的实体一起使用,这些实体最初来自 1 个 API 调用。它看起来像 3 个单独的文件,但不清楚它们之间如何共享数据。
这就是我的旅行createSlice
的样子
/**
* Get trip by ID action
*/
export const getTripByID = createAsyncThunk(
'trips/getTripByID',
async ( uid ) =>
const response = await findOne(uid)
const normalized = normalize(response, trip)
return normalized.entities
,
)
const tripsAdapter = createEntityAdapter(
selectId: entity => entity.trip_id,
sortComparer: (a, b) => b.start_date.localeCompare(a.start_date),
loading: '',
error: '',
data: [],
)
export const
selectById: selectTripById,
selectIds: selectTripIds,
selectEntities: selectTripEntities,
selectAll: selectAllTrips,
selectTotal: selectTotalTrips,
= tripsAdapter.getSelectors(state => state.trip)
const initialState = tripsAdapter.getInitialState()
const tripSlice = createSlice(
name: 'trips',
initialState,
extraReducers: builder =>
builder.addCase(getAllTrips.fulfilled, (state, payload ) =>
tripsAdapter.upsertMany(state, payload)
state.loading = false
)
builder.addCase(getTripByID.fulfilled, (state, payload ) =>
console.log('payload', payload)
tripsAdapter.upsertMany(state, payload)
state.loading = false
)
,
)
export default tripSlice.reducer
从await findOne(uid)
返回的API响应
created_by: "6040c2d1-ea57-43b6-b5f2-58e84b220f4e",
deleted_by: null,
destination: "Valencia",
end_date: "2020-10-04",
start_date: "2020-09-27",
trip_id: "34a620e8-51ff-4572-b466-a950a8ce1c8a",
uid: "14047a5b-2fe5-46c9-b7f2-e9b5d14db05b",
updated_by: null,
trip_items: [
destination: "Mezzanine Level Shivaji Stadium Metro Station, Baba Kharak Singh Rd, Hanuman Road Area, Connaught Place, New Delhi, Delhi 110001, India",
end_date: "2020-09-28",
end_time: "2020-09-28T01:20:15.906Z",
note: null,
start_date: "2020-09-28",
start_time: "2020-09-28T01:20:15.906Z",
trip_item_id: "bd775be7-2129-42c0-a231-5a568b0f565d",
trips_items_members: [
trip_item_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc",
uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02",
],
uid: "e5f81a6d-1a0d-4456-9d4e-579e80bc27d8",
],
trips_members: [
trip_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc",
uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02",
role: "ADMIN"
]
这是我的 normalizr 架构
const tripItemMember = new schema.Entity(
'trips_items_members',
,
idAttribute: 'trip_item_member_id' ,
)
const tripItem = new schema.Entity(
'trips_items',
trips_items_members: [tripItemMember],
,
idAttribute: 'trip_item_id',
,
)
const tripMember = new schema.Entity(
'trips_members',
,
idAttribute: 'trip_member_id',
,
)
export const trip = new schema.Entity(
'trip',
trips_items: [tripItem],
trips_members: [tripMember],
,
idAttribute: 'trip_id',
,
)
这是 normalizr 的输出
trip:
"34a620e8-51ff-4572-b466-a950a8ce1c8a":
created_by: "6040c2d1-ea57-43b6-b5f2-58e84b220f4e"
deleted_by: null
destination: "Valencia"
end_date: "2020-10-04"
start_date: "2020-09-27"
trip_id: "34a620e8-51ff-4572-b466-a950a8ce1c8a"
trips_items: ["bd775be7-2129-42c0-a231-5a568b0f565d"]
trips_members: ["76b54a80-4d09-4768-bc5a-4d7e153e66dc"]
uid: "14047a5b-2fe5-46c9-b7f2-e9b5d14db05b"
updated_by: null
trips_items:
"0a56da0f-f13b-4c3d-896d-30bccbe48a5a":
destination: "Mezzanine Level Shivaji Stadium Metro Station"
end_date: "2020-09-28"
end_time: "2020-09-28T01:20:15.906Z"
note: null
start_date: "2020-09-28"
start_time: "2020-09-28T01:20:15.906Z"
trip_item_id: "0a56da0f-f13b-4c3d-896d-30bccbe48a5a"
trips_items_members: []
uid: "25d20a9d-1eb9-4226-926d-4d743aa9d5dc"
trips_members:
"76b54a80-4d09-4768-bc5a-4d7e153e66dc":
role: "ADMIN"
trip_member_id: "76b54a80-4d09-4768-bc5a-4d7e153e66dc"
uid: "4b88f9af-8639-4bb0-93fa-96fe97e03d02"
【问题讨论】:
【参考方案1】:您的设置非常类似于 redux-toolkit 文档中的 this detailed example。他们正在获取articles
,但每篇文章都带有嵌入的users
和comments
。它们为三个实体中的每一个定义了单独的切片。
comments
切片没有自己的 action 或 reducer,但它使用 extraReducers
属性来响应文章收到的 action 并存储嵌入的 cmets。
const commentsAdapter = createEntityAdapter();
export const slice = createSlice(
name: "comments",
initialState: commentsAdapter.getInitialState(),
reducers: ,
extraReducers:
[fetchArticle.fulfilled]: (state, action) =>
commentsAdapter.upsertMany(state, action.payload.comments);
);
fetchArticle
操作由 article
切片“拥有”,但操作负载包含所有三种类型的实体。 所有切片都接收所有操作,因此comments
和users
能够使用自己的逻辑响应此操作。每个切片对其他切片能做什么或不能做什么没有任何影响。
在您的情况下,您想为 items
和 members
创建切片。您不想调用upsertMany(state, payload)
,而是希望负载按实体类型作为键,以便您可以调用upsertMany(state, payload.members)
。
【讨论】:
以上是关于Redux - createSlice 内的标准化嵌套数据组织的主要内容,如果未能解决你的问题,请参考以下文章
对 redux 工具包的 createSlice 中的多个操作做出反应
如何正确使用 redux 工具包的 preloadedState 和 createSlice?
如何将 redux-toolkit createSlice 与 React 类组件一起使用
如何重用 Redux Toolkit createSlice 函数中的 reducer 逻辑?
如何使用 redux-toolkit 中的 createSlice 方法在不同的 reducer 函数中使用单个动作类型