Привязка редукторов к подмножеству состояния, динамически устанавливаемому

В настоящее время я начинаю с Redux, и мне неясно, как правильно привязать редуктор к динамически устанавливаемым суб-частям состояния.

Например, предположим, что мое состояние выглядит так (после асинхронного получения некоторых данных из серверных API)

{
   "categories": {
      "collection": {
        "42": {
           "id": "42",
           "title": "whatever",
           "owner_id": "10"
           "posts": {
              "collection": {
                "36": {
                   "id": "36", 
                   "title": "hello",
                   "content": "hello world"
                },
                "37": { // more posts ... } 
              },
              "ids": ["36", "37", ...]
           },
           "otherChildren": { // more sub-entities } 
        },
        "43": { // more categories ... }
      },
      "ids": ["42", "43", ...]
   },
   "users": {
     "collection": {
       "10": {
          "id": "10"
          "email": "[email protected]"
       },
       "11": { // more users ... } 
     },
     "ids": [10, 11]
   }
}

Мой корневой редуктор будет выглядеть так:

export default combineReducers({
    categories: categoriesReducer,  
    users: usersReducer
})

и категорииReducer :

function categoriesReducer(state = initialState, action) {
  switch (action.type) {
    case FETCH_ALL_CATEGORIES_SUCCESS:
      return Object.assign({}, state, {
        categories: { 
          collection: action.payload 
        } 
      })
    default:
      return state
  }
}

Теперь то, что я хотел бы сделать, это легко делегировать обработку части подмножества post состояния с помощью функции postsReducer, в основном добавляя случай, например:

  case FETCH_CATEGORY_ALL_POSTS_SUCCESS:
    let categoryId = action.categoryId 
    return Object.assign({}, state, {
      categories: {
        [categoryId]: combineReducers({
           "posts": postsReducer,
           "otherChildren": otherChildrenReducer
        })
      }
    }

Конечно, это не работает. Чего я не понимаю, так это того, как заставить редукс автоматически обновлять подмножество состояния, используя combReducer для вложенных редукторов, при этом автоматически передавая правильное подмножество в качестве аргумента состояния функции редуктора и не переопределяя существующие данные (т.е. категория в мой пример).

Каким-то образом мне удалось выполнить эту работу, написав свою собственную функцию «делегата», но это кажется довольно неправильным, особенно глядя на https://github.com/reactjs/redux/blob/master/src/combineReducers.js., который выглядит именно так.

Как я обычно должен это делать? Можно ли вообще использовать combReducers таким образом с Redux, я неправильно понимаю смысл combReducer или я ожидаю от него много волшебства?

Спасибо !

ИЗМЕНИТЬ/ОБНОВИТЬ:

Мне действительно нужно, чтобы они были вложенными (правильно, может быть, пример category/post не правильный), и я хотел бы, чтобы редуктор (то есть здесь, postsReducer, но это может быть редуктор Collection) можно было повторно использовать в несколько мест.

(Почему я хочу, чтобы он был вложенным? На самом деле в этом примере допустим, что один post может принадлежать только одному category, поскольку данные post на самом деле зашифрованы закрытым ключом из category. Вот почему мне так логично изображать эту цепочку, это отношение в государстве)

Нет ли способа с редуксом делегировать другому редьюсеру при передаче надлежащего подмножества состояния - то есть, например, передать состояние categories.collection.42.posts.collection.36 в postReducer ?


person nakwa    schedule 02.03.2017    source источник
comment
Что бы это ни стоило, ничего не обновляется автоматически. Редьюсеры — это просто функции. Вы можете прочитать redux.js.org/docs/recipes/StructuringReducers.html чтобы получить некоторые идеи.   -  person markerikson    schedule 02.03.2017
comment
Если вы хотите иметь похожие или точно такие же редукторы для разных объектов данных, рассмотрите возможность написания генератора редукторов: redux.js.org/docs/recipes/, также здесь: redux.js.org/docs/recipes/reducers/ReusingReducerLogic.html. Делегировать можно, но только в одном направлении (от менее конкретного к более конкретному). Конечным результатом является дерево редуктора. Но то, о чем вы говорите, звучит как круговое делегирование.   -  person NateQ    schedule 02.03.2017
comment
Если вы действительно хотите вложить один тип объекта данных в другой в хранилище, вы, конечно, можете это сделать, если реализуете редюсеры в виде дерева. Как предложил Маркриксон выше, я также предлагаю вам вернуться и прочитать документы на redux.js.org, они повторно очень хорошо написано и полезно.   -  person NateQ    schedule 02.03.2017


Ответы (2)


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

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

{
   categories: [],
   posts: [],
   users: []
}

Тогда ваши категории будут содержать только идентификаторы сообщений.

Таким образом, вы можете «захватить» действие «FETCH_ALL_CATEGORIES_SUCCESS» в обоих редукторах (категории и редуктор новых сообщений), в categoryReducer вы сохраните данные категории и идентификаторы, а postsReducer просто сохранит сообщения.

person Borjante    schedule 02.03.2017
comment
спасибо @Borjante. Я обновляю вопрос, добавляя еще несколько деталей, см. Выше! - person nakwa; 02.03.2017

Вы неправильно используете combReducers в последнем фрагменте кода. (Это правильно в первом фрагменте, где вы используете его для объединения категорийReducer и usersReducer вместе.)

В случае FETCH_CATEGORY_ALL_POSTS_SUCCESS вместо вызова combReducers просто вызовите простую функцию внутри categoryReducer.

posts: posts(action.payload.posts)

Эта простая функция в основном является вспомогательным редуктором. Это было бы то, что вы написали сами, и вы, вероятно, поместили бы это в тот же файл.

Но две другие основные проблемы с вашим кодом выше:

1) Как уже сказал другой пользователь выше, вероятно, было бы не лучше хранить сообщения как подсвойство категорий. Лучше хранить сообщения как отдельный элемент в дереве состояний и просто иметь поле category_id для каждого сообщения, чтобы показать, к какой категории оно принадлежит. Таким образом, ваше дерево состояний в этом случае будет выглядеть так:

{
  categories: {},
  posts: {},
  users: {}
}

и вы бы использовали combReducers изначально как:

export default combineReducers({
  categories: categoriesReducer,  
  posts: postsReducer,
  users: usersReducer
})

2) В categoryReducer (или любом другом, если на то пошло) вам не нужно иметь свойство categories, потому что вы уже создали это свойство при вызове combineReducers. Другими словами, в categoriesReducer это должно быть так:

function categoriesReducer(state = initialState, action) {
  switch (action.type) {
    case FETCH_ALL_CATEGORIES_SUCCESS:
      return Object.assign({}, state, {
        collection: action.payload,
        ids: // function to extract ids goes here
      });
    default:
      return state;
  }
}
person NateQ    schedule 02.03.2017
comment
спасибо @NateQ. Я обновляю вопрос, добавляя еще несколько деталей, см. выше! - person nakwa; 02.03.2017