Redux: объедините `reducres` для не плоского состояния с избеганием создания большого количества редукторов - PullRequest
0 голосов
/ 31 октября 2018

Я изучаю Redux и пытаюсь создать и объединить редукторы для простой модели для школьников, классов и студентов. Я хочу реализовать такую ​​структуру моего состояния :

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 во второй школе? Это значит, что я должен указать школьный идентификатор ... Но если мне нужно добавить телефон ученика, мне нужно указать идентификаторы каждого ученика для получения необходимого ученика (т. Е. Идентификатор школы, класса и ученика) ... возможно, моя текущая реализация неверна ... Я уже не уверен ...

Я в замешательстве. : (((Я понимаю, как использовать 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(???);

Как правильно создавать и комбинировать редукторы для такого не плоского состояния?

1 Ответ

0 голосов
/ 31 октября 2018

Я понимаю, что могу создать редуктор для каждого свойства, но это будет очень тяжело, если модель будет большой.

Я не понимаю вашу точку зрения, поскольку вы уже знаете, как плохо обновлять вложенную структуру: вам нужно углубляться, искать поля и тщательно обрабатывать обновления, чтобы не сломать существующие данные. Хуже того, с вашей вложенной структурой стоимость рендеринга компонентов React будет высокой, поскольку обновление номера телефона потребует от вас глубокого клонирования практически всего.

Обычно мой образ избыточного состояния - это база данных sql на стороне клиента, где каждая модель (например, школа, класс, ученик) должна храниться в отдельных таблицах; child должен содержать родительский идентификатор, а parent может содержать дочерние идентификаторы как массив для двустороннего поиска.

Хороший способ - разбить ваш редуктор на отдельные редукторы для каждой модели и использовать некоторые промежуточные программы, такие как redux-thunk или redux-saga, для обработки обновлений в связанных моделях при добавлении - удалении чего-либо.

Если вам лень что-то ломать, то 1 редуктор все еще в порядке; но вам нужно нормализовать данные для лучшей обработки данных:

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"]
    }
  }
}

Как вы реализуете действия для обработки данных обновления, это ваша собственная проблема, но с вышеуказанной структурой вам будет легче ее решить.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...