如何对状态中的递归嵌套数据建模
How to model recursively nested data in state
我的数据结构类型如下:
export interface IGroup {
id: number;
name: string;
groupTypeId: number;
items: IItem[];
groups: IGroup[];
}
递归表示"Group"和"Group"以及"Group"和"Item"之间的多对多关系。组由项目和子组组成。项目仅派生为简单类型和其他元数据,但不能有子项。单个组代表层次结构的顶部。
我目前有组件、挂钩等递归地获取一个组并创建一个 edit/create 表单,如下所示:
我有这个表格"working",其中包含测试数据以在保存时生成标准数据输出,如下所示:
{
"1-1": {
"name": "ParentGroup",
"groupType": 2
},
"2-4": {
"name": "ChildGroup1",
"groupType": 1
},
"2-9": {
"name": "ChildGroup2",
"groupType": 3
},
"2-1": {
"itemType": "FreeForm",
"selectedName": "Testing",
"selectedClass": 5
},
"2-2": {
"itemType": "FreeForm",
"selectedName": "DisplayTest",
"selectedClass": 5
},
"3-4": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900503,
"name": "TRUE"
}
},
"3-5": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900502,
"name": "FALSE"
}
},
"3-9": {
"itemType": "FreeForm",
"selectedName": "Test",
"selectedClass": 5
},
"3-10": {
"itemType": "FreeForm",
"selectedName": "Tester",
"selectedClass": 5
},
"3-11": {
"itemType": "FreeForm",
"selectedName": "TestTest",
"selectedClass": 5
}
}
这些对象的 "key" 是网格列和行,因为没有其他保证的唯一标识符(如果用户正在编辑,那么预计组在数据库中有 ID,但如果用户正在表单中添加新组。否则,名称是一个可以更改的输入表单。)这是有道理的,并且很容易以这种方式对键进行建模。如果将另一个组或项目添加到层次结构中,则可以添加它的列和行。
我遇到的问题是,我希望能够有一个添加按钮,该按钮可以添加到组项目或组数组中,以便可以在层次结构中创建新行。我的表格应该处理这些新条目。
例如
"1-1": {
groups: [..., {}],
items: [..., {}]
}
但我唯一的数据结构是深度嵌套的 IGroup。这不利于用作状态和添加到这个深度嵌套的状态。
我遇到的另一个问题是我需要能够将项目和组映射到它们的位置,以便我可以将多对多表转换为相应的数据库并插入新的 groups/items.
建议的解决方案:
我在想,与其将一个组放入我的递归组件中,不如创建规范化对象来存储状态。我会有一个由列行键控的对象,它将包含所有组。另一个由列行键控以保存所有项目。然后我想我还需要两个对象来保持多对多关系,例如组到组和组到项目。
从表单中获取数据后,我希望可以遍历这些状态对象,以这种方式找到层次结构,然后 post 向数据库提供必要的数据。
我看到有很多数据结构来保存这些数据,我不确定这是否是根据我的建模结构实现此目的的最佳方式。我也刚开始使用 Redux Toolkit,所以我对 reducers 有点熟悉,但还不足以了解如何在此处应用它们来帮助我。我一直在努力解决这个问题,非常感谢任何帮助或指导来简化这件事。
进行标准化。每个实体都有一个单一的真实来源使得读取和写入状态变得更加容易。
要做到这一点,请尝试 normalized-reducer。这是一个简单的高阶减速器,学习曲线低。
这是一个有效的 CodeSandbox example of it 实现的 group/item 复合树,与您的问题非常相似。
基本上,您将定义树的架构:
const schema = {
group: {
parentGroupId: { type: 'group', cardinality: 'one', reciprocal: 'childGroupIds' },
childGroupIds: { type: 'group', cardinality: 'many', reciprocal: 'parentGroupId' },
itemIds: { type: 'item', cardinality: 'many', reciprocal: 'groupId' }
},
item: {
groupId: { type: 'group', cardinality: 'one', reciprocal: 'itemIds' }
}
};
然后传入库的顶层函数:
import normalizedSlice from 'normalized-reducer';
export const {
emptyState,
actionCreators,
reducer,
selectors,
actionTypes,
} = normalizedSlice(schema);
然后将 reducer
连接到您的应用程序(适用于 React useReducer
和 Redux store reducers),并使用 selectors
和 actionCreators
读取并写入状态。
我的数据结构类型如下:
export interface IGroup {
id: number;
name: string;
groupTypeId: number;
items: IItem[];
groups: IGroup[];
}
递归表示"Group"和"Group"以及"Group"和"Item"之间的多对多关系。组由项目和子组组成。项目仅派生为简单类型和其他元数据,但不能有子项。单个组代表层次结构的顶部。
我目前有组件、挂钩等递归地获取一个组并创建一个 edit/create 表单,如下所示:
我有这个表格"working",其中包含测试数据以在保存时生成标准数据输出,如下所示:
{
"1-1": {
"name": "ParentGroup",
"groupType": 2
},
"2-4": {
"name": "ChildGroup1",
"groupType": 1
},
"2-9": {
"name": "ChildGroup2",
"groupType": 3
},
"2-1": {
"itemType": "FreeForm",
"selectedName": "Testing",
"selectedClass": 5
},
"2-2": {
"itemType": "FreeForm",
"selectedName": "DisplayTest",
"selectedClass": 5
},
"3-4": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900503,
"name": "TRUE"
}
},
"3-5": {
"itemType": "EnumValue",
"selectedItem": {
"id": 12900502,
"name": "FALSE"
}
},
"3-9": {
"itemType": "FreeForm",
"selectedName": "Test",
"selectedClass": 5
},
"3-10": {
"itemType": "FreeForm",
"selectedName": "Tester",
"selectedClass": 5
},
"3-11": {
"itemType": "FreeForm",
"selectedName": "TestTest",
"selectedClass": 5
}
}
这些对象的 "key" 是网格列和行,因为没有其他保证的唯一标识符(如果用户正在编辑,那么预计组在数据库中有 ID,但如果用户正在表单中添加新组。否则,名称是一个可以更改的输入表单。)这是有道理的,并且很容易以这种方式对键进行建模。如果将另一个组或项目添加到层次结构中,则可以添加它的列和行。
我遇到的问题是,我希望能够有一个添加按钮,该按钮可以添加到组项目或组数组中,以便可以在层次结构中创建新行。我的表格应该处理这些新条目。 例如
"1-1": {
groups: [..., {}],
items: [..., {}]
}
但我唯一的数据结构是深度嵌套的 IGroup。这不利于用作状态和添加到这个深度嵌套的状态。
我遇到的另一个问题是我需要能够将项目和组映射到它们的位置,以便我可以将多对多表转换为相应的数据库并插入新的 groups/items.
建议的解决方案: 我在想,与其将一个组放入我的递归组件中,不如创建规范化对象来存储状态。我会有一个由列行键控的对象,它将包含所有组。另一个由列行键控以保存所有项目。然后我想我还需要两个对象来保持多对多关系,例如组到组和组到项目。
从表单中获取数据后,我希望可以遍历这些状态对象,以这种方式找到层次结构,然后 post 向数据库提供必要的数据。
我看到有很多数据结构来保存这些数据,我不确定这是否是根据我的建模结构实现此目的的最佳方式。我也刚开始使用 Redux Toolkit,所以我对 reducers 有点熟悉,但还不足以了解如何在此处应用它们来帮助我。我一直在努力解决这个问题,非常感谢任何帮助或指导来简化这件事。
进行标准化。每个实体都有一个单一的真实来源使得读取和写入状态变得更加容易。
要做到这一点,请尝试 normalized-reducer。这是一个简单的高阶减速器,学习曲线低。
这是一个有效的 CodeSandbox example of it 实现的 group/item 复合树,与您的问题非常相似。
基本上,您将定义树的架构:
const schema = {
group: {
parentGroupId: { type: 'group', cardinality: 'one', reciprocal: 'childGroupIds' },
childGroupIds: { type: 'group', cardinality: 'many', reciprocal: 'parentGroupId' },
itemIds: { type: 'item', cardinality: 'many', reciprocal: 'groupId' }
},
item: {
groupId: { type: 'group', cardinality: 'one', reciprocal: 'itemIds' }
}
};
然后传入库的顶层函数:
import normalizedSlice from 'normalized-reducer';
export const {
emptyState,
actionCreators,
reducer,
selectors,
actionTypes,
} = normalizedSlice(schema);
然后将 reducer
连接到您的应用程序(适用于 React useReducer
和 Redux store reducers),并使用 selectors
和 actionCreators
读取并写入状态。