Logo
Section 1 / 8

添加实体 CRUD 操作的 Reducer

添加实体创建与删除 Reducer,处理表单草稿并完成重置取消

导入

添加实体 CRUD 操作的 Reducer

添加实体创建与删除 Reducer,处理表单草稿并完成重置取消

在《表单变更处理、数据编辑与特性还原器》一节中,我们使用了自定义的 FormEditWrapper 组件来缓冲表单变更事件,编写了针对“编辑”特性的自定义还原器逻辑,并为飞行员条目添加了基础编辑功能。在本节中,我们将增加删除飞行员的功能,利用通用实体还原器逻辑实现表单的“草稿数据”处理,并完成表单重置与取消操作。

截至目前,我们已经能够从假想 API 加载飞行员列表,在 UI 中显示它们,并编辑单个飞行员的详细信息。这涵盖了“CRUD”(创建、检索、更新、删除)中的两项功能。现在,让我们继续为其他情况添加更多还原器层面的 CRUD 逻辑。

创建 Redux-ORM 会话选择器

首先进行一些清理工作,这也将带来轻微的性能提升。正如在《性能》章节中提到的,我们的许多连接组件都在其 mapState 函数中创建 Redux-ORM 会话实例。虽然创建这些会话实例的开销可能并不大,但完全没有必要在每次状态更新时都创建多个会话实例,然后立即丢弃它们。这一点尤其重要,因为一个会话实例封装了一个特定的状态“表格”对象,而由于所有组件都只进行读取操作而非写入,它们完全可以共享同一个会话实例,不会产生任何问题。

此外,当前我们的 mapState 函数直接访问 state.entities 并将其传递给模式。显式地访问状态切片本身并没有错,但使用选择器函数来封装这些查找操作是一种良好的实践。

我们可以同时解决这两个问题:创建一个记忆化选择器函数,该函数对于给定版本的 entities 状态切片始终返回相同的会话实例。(作为提醒,“记忆化”指的是缓存输入和输出,当遇到相同输入时,可以直接返回之前计算的结果,跳过重复计算。)Reselect 库提供了一个名为 createSelector 的函数,可以生成我们所需的记忆化选择器。这样一来,我们每更新一次模型数据,就只会创建一个会话实例。尽管我们的应用规模较小,可能在性能基准测试中甚至察觉不到差异,但这仍然是朝着正确方向迈出的一步。

提交 ec77c77:使用选择器确保每次状态更新仅有一个会话实例

features/entities/entitySelectors.js

1import {createSelector} from "reselect"; 2 3import orm from "app/orm"; 4 5export const selectEntities = state => state.entities; 6 7export const getEntitiesSession = createSelector( 8 selectEntities, 9 entities => orm.session(entities) 10);

选择器的创建非常简单:我们创建一个“输入”选择器,用于返回 state.entities 切片,并将其作为第一个参数传给 Reselect 提供的 createSelector 函数。而“输出”选择器接收返回的 entities 切片,调用 orm.session(entities) 来创建会话实例。该输出选择器仅在 entities 对象发生变化时才会运行。

接下来,我们更新所有连接组件以使用这个选择器:

features/pilots/PilotDetails/PilotDetails.jsx

1import {connect} from "react-redux"; 2import {Form, Dropdown, Grid, Button} from "semantic-ui-react"; 3 4-import orm from "app/orm"; 5+import {getEntitiesSession} from "features/entities/entitySelectors"; 6 7const mapState = (state) => { 8 let pilot; 9 10 const currentPilot = selectCurrentPilot(state); 11 12- const session = orm.session(state.entities); 13+ const session = getEntitiesSession(state); 14 const {Pilot} = session;

在将所有组件更新为使用 getEntitiesSession() 后,我们应仅在每次 entities 更新时创建一个会话实例。

添加实体创建与删除还原器

我们之前已创建了一个“特性还原器”来处理适用于 entities 切片的通用动作。当时,我们只实现了一个案例还原器,用于响应 ENTITY_UPDATE 动作,从而允许我们对正在编辑的飞行员条目进行修改。

现在我们将在此部分再添加两个案例还原器:一个用于创建新实体,另一个用于删除实体。我们目前需要删除还原器来支持删除飞行员条目,而创建还原器将在稍后发挥作用。

提交 779dbdc:添加通用实体创建与删除处理

features/entities/entityReducer.js

1-import {ENTITY_UPDATE} from "./entityConstants"; 2+import { 3+ ENTITY_UPDATE, 4+ ENTITY_DELETE, 5+ ENTITY_CREATE, 6+} from "./entityConstants"; 7 8export function updateEntity(state, payload) { 9 const {itemType, itemID, newItemAttributes} = payload; 10 11 const session = orm.session(state); 12 const ModelClass = session[itemType]; 13 14 let newState = state; 15 16 if(ModelClass.hasId(itemID)) { 17 const modelInstance = ModelClass.withId(itemID); 18 19 modelInstance.update(newItemAttributes); 20 21 newState = session.state; 22 } 23 24 return newState; 25} 26 27+export function deleteEntity(state, payload) { 28+ const {itemID, itemType} = payload; 29+ 30+ const session = orm.session(state); 31+ const ModelClass = session[itemType]; 32+ 33+ let newState = state; 34+ 35+ if(ModelClass.hasId(itemID)) { 36+ const modelInstance = ModelClass.withId(itemID); 37+ 38+ modelInstance.delete(); 39+ 40+ newState = session.state; 41+ } 42+ 43+ return newState; 44+} 45+ 46+export function createEntity(state, payload) { 47+ const {itemType, newItemAttributes} = payload; 48+ 49+ const session = orm.session(state); 50+ const ModelClass = session[itemType]; 51+ 52+ ModelClass.parse(newItemAttributes); 53+ 54+ return session.state; 55+} 56+ 57 58const entityHandlers = { 59 [ENTITY_UPDATE] : updateEntity, 60+ [ENTITY_CREATE] : createEntity, 61+ [ENTITY_DELETE] : deleteEntity, 62}; 63 64const entityCrudFeatureReducer = createConditionalSliceReducer("entities", entityHandlers);

所有这些还原器都遵循相同的通用模式:从当前状态创建 Redux-ORM 会话,根据名称查找绑定到会话的模型类,应用更改,然后返回更新后的状态。(是的,也许可以编写某种包装函数来消除这些函数中的一些重复行为,但由于它们都很简短,这种优化并不值得投入额外精力。)

请注意,createEntity() 还原器假设每个模型类都具有静态的 parse() 方法。根据《加载数据》章节所述,这并非 Redux-ORM 内建功能,而是我们在所有模型类中编写此类方法所遵循的一种约定。

表单变更处理、数据编辑与功能 Reducer

实现飞行员删除