javascript - Redux:将用于非平坦状态的 `reducres` 与避免创建大量 reducer 相结合
问题描述
我学习 Redux 并尝试为学校、班级和学生创建和组合reducer以实现简单模型。我想实现我的状态的这种结构:
const model = {
schools:[
{ id: "91cb54b3-1289-4520-abe1-d8826d39fce3",
name: "School #25", address: "Green str. 12",
classes: [
{ id: "336ff233-746f-441b-84c7-0e6c275a7e24", name: "1A",
students: [
{ id: "475dd06e-a52d-4d90-aa07-46eab7c029a7", name: "Ivan Ivanov",
age: 7, phones: ["+7-123-456-78-90"] }
]
}
]
}
]
};
我知道我可以为每个属性创建减速器,但如果模型很大,那就很难了。因此,我想尽量减少减速器的数量。我认为解决方案会很简单,但我的减速器组合遇到了问题......
另外,我看到了添加问题...例如,对于我当前的实现,如何class
在第二所学校添加实例?这意味着我要指向学校ID......但是如果我需要添加学生的电话,那么我需要指向每个家长的ID以获得必要的学生(即学校,班级和学生的ID)......它是可能我当前的实现是错误的......我不确定......
我很困惑。:((( 我了解如何combineReducers
用于简单的平面模型,但我不知道如何在更复杂的情况下使用...
这是我学习 Redux 并尝试将其combineReducers
用于我的“商业模式”的“沙盒”:
import {createStore} from "redux";
import {uuidv4} from "uuid/v4"; // yarn add uuid
const createId = uuidv4; // creates new GUID
const deepClone = object => JSON.parse(JSON.stringify(object));
/**
I will use simple model: the shools, classes, and students:
const model = {
schools:[
{ id: "91cb54b3-1289-4520-abe1-d8826d39fce3",
name: "School #25", address: "Green str. 12",
classes: [
{ id: "336ff233-746f-441b-84c7-0e6c275a7e24", name: "1A",
students: [
{ id: "475dd06e-a52d-4d90-aa07-46eab7c029a7", name: "Ivan Ivanov",
age: 7, phones: ["+7-123-456-78-90"] }
]
}
]
}
]
};
*/
// ================= Business model ====================
function createSchool(name = "", address = "", classes = []){
return { id: createId(), name, address, classes };
}
function createClass(name = "", students = []){
return { id: createId(), name, students };
}
function createStudent(name = "", age = 0, phones = []){
return { id: createId(), name, age, phones };
}
function createPhone(phone = ""){
return { id: createId(), phone };
}
// ================= end of Business model =============
const ACTION_KEYS = { // It is used by Action model
CREATE_SCHOOL: "CREATE_SCHOOL",
UPDATE_SCHOOL: "UPDATE_SCHOOL",
DELETE_SCHOOL: "DELETE_SCHOOL",
CREATE_CLASS: "CREATE_CLASS",
UPDATE_CLASS: "UPDATE_CLASS",
DELETE_CLASS: "DELETE_CLASS",
CREATE_STUDENT: "CREATE_STUDENT",
UPDATE_STUDENT: "UPDATE_STUDENT",
DELETE_STUDENT: "DELETE_STUDENT",
CREATE_PHONE: "CREATE_PHONE",
UPDATE_PHONE: "UPDATE_PHONE",
DELETE_PHONE: "DELETE_PHONE",
}
// ==================== Action model ======================
// School actions:
function create_createShoolAction(value = createSchool()){
// use createSchool() function for 'value' initializing
return {type: ACTION_KEYS.CREATE_SCHOOL, value };
}
function create_updateShoolAction(value){
// use createSchool() function for 'value' initializing
return {type: ACTION_KEYS.UPDATE_SCHOOL, value };
}
function create_deleteShoolAction(id){
return {type: ACTION_KEYS.DELETE_SCHOOL, id };
}
// Class actions:
function create_createClassAction(value = createClass()){
// use createClass() function for 'value' initializing
return {type: ACTION_KEYS.CREATE_CLASS, value };
}
function create_updateClassAction(value){
// use createClass() function for 'value' initializing
return {type: ACTION_KEYS.UPDATE_CLASS, value };
}
function create_deleteClassAction(id){
return {type: ACTION_KEYS.DELETE_CLASS, id };
}
// Student actions:
function create_createStudentAction(value = createStudent()){
// use createStudent() function for 'value' initializing
return {type: ACTION_KEYS.CREATE_STUDENT, value };
}
function create_updateStudentAction(value){
// use createStudent() function for 'value' initializing
return {type: ACTION_KEYS.UPDATE_STUDENT, value };
}
function create_deleteStudentAction(id){
return {type: ACTION_KEYS.DELETE_STUDENT, id };
}
// Phone actions:
function create_createPhoneAction(value = createPhone()){
// use createPhone() function for 'value' initializing
return {type: ACTION_KEYS.CREATE_PHONE, value };
}
function create_updatePhoneAction(value){
// use createPhone() function for 'value' initializing
return {type: ACTION_KEYS.UPDATE_PHONE, value };
}
function create_deletePhoneAction(id){
return {type: ACTION_KEYS.DELETE_PHONE, id };
}
// ==================== end of Action model ===============
// ========================= Reducers =====================
// This function contains common implementation for all my reducers (I'm lazy).
function reducer(state = [], action, action_keys){
switch(action.type){
switch action_keys[0]: { // create new item
return [...deepClone(state), ...deepClone(action.value)];
break;
}
switch action_keys[1]: { // update existing item
const index = state.findIndex(n => n.id === action.value.id);
if(index < 0) return state;
const clonedState = [...deepClone(state)];
return [...clonedState.slice(0, index), ...deepClone(action.value),
...clonedState.slice(index + 1)];
break;
}
switch action_keys[2]: { // delete existing item
const index = state.findIndex(n => n.id === action.id);
if(index < 0) return state;
const clonedState = [...deepClone(state)];
return [...clonedState.slice(0, index), ...clonedState.slice(index + 1)];
break;
}
default: { // otherwise return original
return state;
break;
}
}
}
function schoolReducer(state = [], action){
return reducer(state, action, [
ACTION_KEYS.CREATE_SCHOOL,
ACTION_KEYS.UPDATE_SCHOOL,
ACTION_KEYS.DELETE_SCHOOL
]);
}
function classReducer(state = [], action){
return reducer(state, action, [
ACTION_KEYS.CREATE_CLASS,
ACTION_KEYS.UPDATE_CLASS,
ACTION_KEYS.DELETE_CLASS
]);
}
function studentReducer(state = [], action){
return reducer(state, action, [
ACTION_KEYS.CREATE_STUDENT,
ACTION_KEYS.UPDATE_STUDENT,
ACTION_KEYS.DELETE_STUDENT
]);
}
function phoneReducer(state = [], action){
return reducer(state, action, [
ACTION_KEYS.CREATE_PHONE,
ACTION_KEYS.UPDATE_PHONE,
ACTION_KEYS.DELETE_PHONE
]);
}
// The "top-level" combined reducer
const combinedReducer = combineReducers({
schools: schoolReducer
// Oops... How to build the hierarchy of the remaining reducers (classReducer,
// studentReducer, and phoneReducer)?
});
// =============== end of Reducers =====================
const store = createStore(combinedReducer);
const unsubscribe = store.subscribe(() => console.log("subscribe:",
store.getState()));
// Now to work with the store...
store.dispatch(create_createShoolAction(createShool("Shool #5", "Green str. 7")));
store.dispatch(create_createShoolAction(createShool("Shool #12", "Read str. 15")));
store.dispatch(create_createShoolAction(createShool("Shool #501", "Wall str. 123")));
// Now, how can I add a new class into the "Shool #12" school?
// store.dispatch(???);
对于这种不平坦的状态,如何正确地创建和组合减速器?
解决方案
我知道我可以为每个属性创建减速器,但如果模型很大,那就很难了。
我不明白您的观点,因为您已经体验过更新嵌套结构是多么糟糕:您需要深入研究,搜索字段并仔细处理更新,以免破坏现有数据。更糟糕的是,使用你的嵌套结构,渲染 React 组件的成本会很高,因为更新电话号码需要你深度克隆几乎所有东西。
通常我的 redux 状态图像是一个客户端 sql 数据库,其中每个模型(例如学校、班级、学生)应该存储在单独的表中;child 应该包含 parent id,并且 parent 可以包含 child id 作为双向搜索的数组。
好的方法是将你的 reducer 分解为每个模型的单独的 reducer,并使用一些中间件,如 redux-thunk 或 redux-saga 在你添加 - 删除任何内容时处理相关模型的更新。
如果你懒得分解,那么 1 个 reducer 还是可以的;但是您需要对数据进行规范化以便更好地处理数据:
const initialState = {
schools: {},
classes: {},
students: {}
}
function reducer(state = initialState, actions, action_keys) {
...
}
因此,您的数据样本可能如下所示:
{
schools: {
"91cb54b3-1289-4520-abe1-d8826d39fce3": {
id: "91cb54b3-1289-4520-abe1-d8826d39fce3",
name: "School #25",
address: "Green str. 12",
classes: [
"336ff233-746f-441b-84c7-0e6c275a7e24"
]
}
},
classes: {
"336ff233-746f-441b-84c7-0e6c275a7e24": {
id: "336ff233-746f-441b-84c7-0e6c275a7e24",
schoolId: "91cb54b3-1289-4520-abe1-d8826d39fce3",
name: "1A",
students: [
"475dd06e-a52d-4d90-aa07-46eab7c029a7"
]
}
},
students: {
"475dd06e-a52d-4d90-aa07-46eab7c029a7": {
id: "475dd06e-a52d-4d90-aa07-46eab7c029a7",
classId: "336ff233-746f-441b-84c7-0e6c275a7e24"
name: "Ivan Ivanov",
age: 7,
phones: ["+7-123-456-78-90"]
}
}
}
如何实现处理更新数据的操作是您自己的问题,但使用上述结构,您应该更容易解决。
推荐阅读
- sql - Paginate grouped query results with limit per page
- authentication - 我的身份验证问题是否与双跳问题有关?
- java - 使用 PACT 时如何在 HttpTarget 中传递标头
- sql-server - 具有多个 if 语句时的 SQL 过程行为
- python - Debian AWS 自动启动脚本
- django - 通过父模型文件进行验证
- python - 如何使用 Django 连接到 PostgreSQL 数据库服务器?
- kubernetes - kubectl pod 无法下拉 AWS ECR 映像
- chat - 解析使用 Rocket Chat 发送的消息
- python - 如何在文件名中有一个整数(python)